diff --git a/openapi b/openapi index b639a02180c2b5e301c77483b3a2fa645ba94169..073fd7213fd9e6ebc2f8a47d628a650de30c8bc4 160000 --- a/openapi +++ b/openapi @@ -1 +1 @@ -Subproject commit b639a02180c2b5e301c77483b3a2fa645ba94169 +Subproject commit 073fd7213fd9e6ebc2f8a47d628a650de30c8bc4 diff --git a/server/worldanalysis/src/ETSI.ARF.OpenAPI.WorldAnalysis/ETSI-ARF/ControllersImpl/CapabilitiesImpl.cs b/server/worldanalysis/src/ETSI.ARF.OpenAPI.WorldAnalysis/ETSI-ARF/ControllersImpl/CapabilitiesImpl.cs index f9c1a3543df47330a2fc2d11b904b5e75c1f85a0..4638d5c7df96786128abcc49291f699338163b95 100644 --- a/server/worldanalysis/src/ETSI.ARF.OpenAPI.WorldAnalysis/ETSI-ARF/ControllersImpl/CapabilitiesImpl.cs +++ b/server/worldanalysis/src/ETSI.ARF.OpenAPI.WorldAnalysis/ETSI-ARF/ControllersImpl/CapabilitiesImpl.cs @@ -53,13 +53,16 @@ namespace ETSI.ARF.OpenAPI.WorldAnalysis.Controllers /// </summary> public override IActionResult GetCapabilities([FromHeader (Name = "token")]string token, [FromHeader (Name = "sessionID")]string sessionID) { - // todo: compare token and sessionID + if (!Startup.IsAccessGranted(token)) return StatusCode(511, new Error() { Message = "Invalid token!" }); + + // todo: compare sessionID // Get all capabilities from all anchors/trackables + // Info: Capabilities are collected after a module is connected via websockets // Create list List<Capability> capabilitiesList = new List<Capability>(); - capabilitiesList.AddRange(WorldAnalysisModules.Singleton.GetCapabilities()); + capabilitiesList.AddRange(WorldAnalysisConnections.Singleton.GetCapabilities()); // Create response object GetCapabilities200Response response = new GetCapabilities200Response(); @@ -73,11 +76,13 @@ namespace ETSI.ARF.OpenAPI.WorldAnalysis.Controllers /// </summary> public override IActionResult GetSupport([FromRoute (Name = "trackableOrAnchorUUID")][Required]Guid trackableOrAnchorUUID, [FromHeader (Name = "token")]string token, [FromHeader (Name = "sessionID")]string sessionID) { - // todo: compare token and sessionID + if (!Startup.IsAccessGranted(token)) return StatusCode(511, new Error() { Message = "Invalid token!" }); + + // todo: compare sessionID // Create list List<Capability> capabilitiesList = new List<Capability>(); - capabilitiesList.AddRange(WorldAnalysisModules.Singleton.GetCapabilitiesFromUuid(trackableOrAnchorUUID)); + capabilitiesList.AddRange(WorldAnalysisConnections.Singleton.GetCapabilitiesFromUuid(trackableOrAnchorUUID)); // Create response object GetSupport200Response response = new GetSupport200Response(); diff --git a/server/worldanalysis/src/ETSI.ARF.OpenAPI.WorldAnalysis/ETSI-ARF/ControllersImpl/PoseImpl.cs b/server/worldanalysis/src/ETSI.ARF.OpenAPI.WorldAnalysis/ETSI-ARF/ControllersImpl/PoseImpl.cs index 058e38806c8618f1b8a64de2238996e8eef5c7ac..44606416aab46dfd8512ffebd5068f2ada13de84 100644 --- a/server/worldanalysis/src/ETSI.ARF.OpenAPI.WorldAnalysis/ETSI-ARF/ControllersImpl/PoseImpl.cs +++ b/server/worldanalysis/src/ETSI.ARF.OpenAPI.WorldAnalysis/ETSI-ARF/ControllersImpl/PoseImpl.cs @@ -53,11 +53,13 @@ namespace ETSI.ARF.OpenAPI.WorldAnalysis.Controllers /// </summary> public override IActionResult ConfigureFramerate([FromBody] PoseConfiguration poseConfiguration, [FromHeader(Name = "token")] string token, [FromHeader(Name = "sessionID")] string sessionID) { - // todo: compare token and sessionID + if (!Startup.IsAccessGranted(token)) return StatusCode(511, new Error() { Message = "Invalid token!" }); + + // todo: compare sessionID // Notify the modules that the client need a new framerate - WorldAnalysisModules.Singleton.ConfigureFramerate(poseConfiguration); - return StatusCode(200, "Ok."); + bool result = WorldAnalysisConnections.Singleton.ConfigureFramerate(poseConfiguration); + return result ? StatusCode(200, "Ok.") : StatusCode(405, "Not supported."); } /// <summary> @@ -65,11 +67,13 @@ namespace ETSI.ARF.OpenAPI.WorldAnalysis.Controllers /// </summary> public override IActionResult GetPose([FromRoute(Name = "trackableOrAnchorUUID")][Required] Guid trackableOrAnchorUUID, [FromQuery(Name = "mode")][Required()] ModeWorldAnalysis mode, [FromHeader(Name = "token")] string token, [FromHeader(Name = "sessionID")] string sessionID) { - // todo: compare token and sessionID + if (!Startup.IsAccessGranted(token)) return StatusCode(511, new Error() { Message = "Invalid token!" }); + + // todo: compare sessionID // Request from the modules a new pose of a single UUID Pose result; - result = WorldAnalysisModules.Singleton.GetPose(trackableOrAnchorUUID, mode); + result = WorldAnalysisConnections.Singleton.GetPose(trackableOrAnchorUUID, mode); return new ObjectResult(result); } @@ -78,13 +82,15 @@ namespace ETSI.ARF.OpenAPI.WorldAnalysis.Controllers /// </summary> public override IActionResult GetPoses([FromQuery(Name = "uuid")][Required()] List<UuidAndMode> uuid, [FromHeader(Name = "token")] string token, [FromHeader(Name = "sessionID")] string sessionID) { - // todo: compare token and sessionID + if (!Startup.IsAccessGranted(token)) return StatusCode(511, new Error() { Message = "Invalid token!" }); + + // todo: compare sessionID // Request from the modules new poses from all UUIDs GetPoses200Response result = new GetPoses200Response(); foreach (var item in uuid) { - result.Poses.Add(WorldAnalysisModules.Singleton.GetPose(item.Uuid, item.Mode)); + result.Poses.Add(WorldAnalysisConnections.Singleton.GetPose(item.Uuid, item.Mode)); } return new ObjectResult(result); } @@ -101,10 +107,13 @@ namespace ETSI.ARF.OpenAPI.WorldAnalysis.Controllers public struct SubscriptionInfo { - public Guid uuid; // id of subscription (id is defined by the WA server) - public Guid uuidTarget; // id trackable or anchor - public DateTime timeValidity; //The duration of the validity of the subscription + public Guid uuidSub; // id of subscription (id is defined by the WA server) + + public SubscribeToPoseRequest subscription; + public string webSocket; public Pose pose; + public DateTime timeValidity; + //public PoseCallback callback; } #pragma warning restore CS1591 // Fehlendes XML-Kommentar für öffentlich sichtbaren Typ oder Element @@ -114,11 +123,26 @@ namespace ETSI.ARF.OpenAPI.WorldAnalysis.Controllers /// </summary> public override IActionResult GetSubscription([FromRoute(Name = "subscriptionUUID")][Required] Guid subscriptionUUID, [FromHeader(Name = "token")] string token, [FromHeader(Name = "sessionID")] string sessionID) { - // todo: compare token and sessionID + if (!Startup.IsAccessGranted(token)) return StatusCode(511, new Error() { Message = "Invalid token!" }); - // todo: search for a subscription and send it back + // todo: compare sessionID - return StatusCode(405, "Not supported yet!"); + // todo: search for a subscription and send it back + foreach (var item in m_subscriptionsPoses) + { + if (item.Key == subscriptionUUID) + { + SubscriptionSingle response = new SubscriptionSingle(); + response.Uuid = subscriptionUUID; + response.Target = item.Value.subscription.Target; + response.Mode = item.Value.pose.Mode; + response.Validity = item.Value.timeValidity.Millisecond; + response.WebhookUrl = item.Value.subscription.WebhookUrl; + response.WebsocketUrl = item.Value.webSocket; + return new ObjectResult(response); + } + } + return StatusCode(404, "Not found."); } /// <summary> @@ -126,46 +150,67 @@ namespace ETSI.ARF.OpenAPI.WorldAnalysis.Controllers /// </summary> public override IActionResult SubscribeToPose([FromBody] SubscribeToPoseRequest subscribeToPoseRequest, [FromHeader(Name = "token")] string token, [FromHeader(Name = "sessionID")] string sessionID) { - // todo: compare token and sessionID + // Simulation ON for STF669 pose calcualtion + string _token = token; + Guid parentUUID = Guid.Empty; + if (token.Contains(",")) + { + _token = token.Split(',')[0]; + parentUUID = new Guid(token.Split(',')[1]); + } + + if (!Startup.IsAccessGranted(_token)) return StatusCode(511, new Error() { Message = "Invalid token!" }); + + // todo: compare sessionID + + if (subscribeToPoseRequest.Targets != null && subscribeToPoseRequest.Targets.Count > 0) + { + return StatusCode(404, "Multiple subscriptions are not implemented (targets)."); + } + else if (subscribeToPoseRequest.Modes != null && subscribeToPoseRequest.Modes.Count > 0) + { + return StatusCode(404, "Multiple subscriptions are not implemented (modes)."); + } int validity = subscribeToPoseRequest.Validity; // todo: is to handle here or by the client? + SubscribeToPose200Response response = new SubscribeToPose200Response(); + response.Validity = validity; + response.Uuid = Guid.NewGuid(); + response.Target = subscribeToPoseRequest.Target; + response.Mode = subscribeToPoseRequest.Mode; + response.WebhookUrl = subscribeToPoseRequest.WebhookUrl; + response.WebsocketUrl = ""; + + // Send the websocket connection URL + if (string.IsNullOrEmpty(response.WebhookUrl)) + { + // Notice: starting websocket server is done autom. by the client, when calling "URL:xxx/ws" + // Registering the client is done in the analysis module, so the websocket can send to it pose updates + response.WebsocketUrl = "wss://" + Request.Host.ToString() + "/ws"; + } + // We add the subscription SubscriptionInfo info = new SubscriptionInfo(); - info.uuid = new Guid(); - info.uuidTarget = subscribeToPoseRequest.Target; + info.uuidSub = response.Uuid; + info.webSocket = response.WebsocketUrl; info.timeValidity = DateTime.Now.AddMilliseconds(validity / 1000.0f); + info.pose = new Pose(); - info.pose.Mode = subscribeToPoseRequest.Mode; - m_subscriptionsPoses.Add(info.uuid, info); + info.pose.Mode = response.Mode; - // todo: handle multiple subscriptions ? - // subscribeToPoseRequest.Targets[] - // subscribeToPoseRequest.Modes[] + info.subscription = new SubscribeToPoseRequest(); + info.subscription.Target = response.Target; + info.subscription.Mode = response.Mode; + info.subscription.Validity = response.Validity; + info.subscription.WebhookUrl = response.WebhookUrl; + m_subscriptionsPoses.Add(info.uuidSub, info); + // todo: inform the module(s) that the client will track an anchor/trackable and need the pose // todo: has the module to call GetRelocalizationInformation() then?!? - WorldAnalysisModules.Singleton.SubscribeToPose(info); - - SubscribeToPose200Response response = new SubscribeToPose200Response(); - response.Validity = validity; - response.Uuid = info.uuid; - response.Target = info.uuidTarget; - response.Mode = info.pose.Mode; - - if (string.IsNullOrEmpty(subscribeToPoseRequest.WebhookUrl)) - { - response.WebhookUrl = ""; - response.WebsocketUrl = Request.Host.ToString(); - // Notice: starting websocket server is done autom. by the client, when calling "URL:/ws" + WorldAnalysisConnections.Singleton.SubscribeToPose(info, parentUUID); - // todo: register the client, so the websocket will send to it pose updates - } - else - { - response.WebhookUrl = subscribeToPoseRequest.WebhookUrl; - response.WebsocketUrl = null; - } return new ObjectResult(response); } @@ -174,15 +219,21 @@ namespace ETSI.ARF.OpenAPI.WorldAnalysis.Controllers /// </summary> public override IActionResult UnsubscribeFromPose([FromRoute(Name = "subscriptionUUID")][Required] Guid subscriptionUUID, [FromHeader(Name = "token")] string token, [FromHeader(Name = "sessionID")] string sessionID) { - // todo: compare token and sessionID + if (!Startup.IsAccessGranted(token)) return StatusCode(511, new Error() { Message = "Invalid token!" }); + + // todo: compare sessionID // Remove the subscription from the list? if (m_subscriptionsPoses.ContainsKey(subscriptionUUID)) m_subscriptionsPoses.Remove(subscriptionUUID); + else + { + //return StatusCode(404, new Error() { Message = "Unsubscribe UUID not found!" }); + } // Inform the module(s) that the subscription ended - WorldAnalysisModules.Singleton.UnsubscribeFromPose(subscriptionUUID); + WorldAnalysisConnections.Singleton.UnsubscribeFromPose(subscriptionUUID); - return StatusCode(200, "Ok!"); + return StatusCode(200, new Success() { Message = "Unsubscription successfull." }); } /// <summary> @@ -190,7 +241,9 @@ namespace ETSI.ARF.OpenAPI.WorldAnalysis.Controllers /// </summary> public override IActionResult UpdateSubscription([FromRoute(Name = "subscriptionUUID")][Required] Guid subscriptionUUID, [FromBody] UpdateSubscriptionRequest updateSubscriptionRequest, [FromHeader(Name = "token")] string token, [FromHeader(Name = "sessionID")] string sessionID) { - // todo: compare token and sessionID + if (!Startup.IsAccessGranted(token)) return StatusCode(511, new Error() { Message = "Invalid token!" }); + + // todo: compare sessionID // todo: inform the module(s) that the subscription changed diff --git a/server/worldanalysis/src/ETSI.ARF.OpenAPI.WorldAnalysis/ETSI-ARF/ControllersImpl/WebSocketController.cs b/server/worldanalysis/src/ETSI.ARF.OpenAPI.WorldAnalysis/ETSI-ARF/ControllersImpl/WebSocketController.cs index 8a76568e404ccd1c8b622f84521d46a4453f5327..822931138c2f13ab348f117112b00f5c7fc3360e 100644 --- a/server/worldanalysis/src/ETSI.ARF.OpenAPI.WorldAnalysis/ETSI-ARF/ControllersImpl/WebSocketController.cs +++ b/server/worldanalysis/src/ETSI.ARF.OpenAPI.WorldAnalysis/ETSI-ARF/ControllersImpl/WebSocketController.cs @@ -43,11 +43,13 @@ namespace ETSI.ARF.OpenAPI.WorldAnalysis.Controllers // public class WebSocketController : ControllerBase { + private WebSocket websocket; + static public int WebSocketControllerInstanceCount = 0; + private string currentName = ""; private bool registered = false; private bool firstTime = true; private int timeCnt = 3; - private WebSocket websockets; [HttpGet("/ws")] public async Task Get() @@ -62,7 +64,8 @@ namespace ETSI.ARF.OpenAPI.WorldAnalysis.Controllers } else if (webSocket.State == WebSocketState.Open) { - await HandleClientData(HttpContext, webSocket); + // Go to the loop... + await WebSocketServer_Loop(HttpContext, webSocket); } } else @@ -75,63 +78,36 @@ namespace ETSI.ARF.OpenAPI.WorldAnalysis.Controllers // // Send a line of text // - private async Task SendText(WebSocket webSocket, string text) + static private async Task SendText(WebSocket ws, string msg) { - // Response an OK message - var message = text; + var message = msg; var bytes = Encoding.UTF8.GetBytes(message); var arraySegment = new ArraySegment<byte>(bytes, 0, bytes.Length); - await webSocket.SendAsync(arraySegment, WebSocketMessageType.Text, true, CancellationToken.None); + await ws.SendAsync(arraySegment, WebSocketMessageType.Text, true, CancellationToken.None); } - public void SendText(string text) + public void SendText(string msg) { - // Response an OK message - var message = text; + var message = msg; var bytes = Encoding.UTF8.GetBytes(message); var arraySegment = new ArraySegment<byte>(bytes, 0, bytes.Length); - websockets.SendAsync(arraySegment, WebSocketMessageType.Text, true, CancellationToken.None); + websocket.SendAsync(arraySegment, WebSocketMessageType.Text, true, CancellationToken.None); } // - // Send the time all seconds + // Send a demo pose all seconds // - //[HttpGet("/ws/time")] - private async Task SendTime(WebSocket webSocket) - { - while (true) - { - var message = "Time=" + DateTime.Now.ToLocalTime(); - - if (webSocket.State == WebSocketState.Open) - { - timeCnt--; - if (timeCnt < 0) - { - await SendText(webSocket, "TimeStop"); - break; - } - else await SendText(webSocket, message); - } - else if (webSocket.State == WebSocketState.Closed || webSocket.State == WebSocketState.Aborted) - { - break; - } - Thread.Sleep(1000); - } - } - float rotInc = 0; - private async Task SendDemoPose(WebSocket webSocket) + private async Task SendDemoPose(WebSocket ws) { while (true) { - if (webSocket.State == WebSocketState.Open) + if (ws.State == WebSocketState.Open) { timeCnt--; if (timeCnt < 0) { - await SendText(webSocket, "PoseStop"); + await SendText(ws, "PoseStop"); break; } else @@ -153,13 +129,13 @@ namespace ETSI.ARF.OpenAPI.WorldAnalysis.Controllers string json = pose.ToJson(); - await SendText(webSocket, "Pose=" + json); + await SendText(ws, "NewPose=" + json); } } - else if (webSocket.State == WebSocketState.Closed || webSocket.State == WebSocketState.Aborted) + else if (ws.State == WebSocketState.Closed || ws.State == WebSocketState.Aborted) { - if (WorldAnalysisModules.Singleton.modules.Contains(currentModule)) WorldAnalysisModules.Singleton.modules.Remove(currentModule); - if (WorldAnalysisModules.Singleton.clients.Contains(currentClient)) WorldAnalysisModules.Singleton.clients.Remove(currentClient); + if (WorldAnalysisConnections.Singleton.modules.Contains(currentModule)) WorldAnalysisConnections.Singleton.modules.Remove(currentModule); + if (WorldAnalysisConnections.Singleton.clients.Contains(currentClient)) WorldAnalysisConnections.Singleton.clients.Remove(currentClient); currentClient = null; currentModule = null; break; @@ -171,140 +147,196 @@ namespace ETSI.ARF.OpenAPI.WorldAnalysis.Controllers private Module currentModule; private Client currentClient; - private async void OnReceiveText(WebSocket webSocket, string msg) + private async void OnReceiveText(string msg) { + #region Register the client/module if (firstTime) { - #region Register the client/module - if (msg.StartsWith("RegisterModule:")) + if (msg.StartsWith("RegisterModule=")) { registered = true; firstTime = false; - currentName = msg.Split(':')[1]; + currentName = msg.Split('=')[1]; if (currentModule == null && currentClient == null) { currentModule = new Module(); currentModule.name = currentName; currentModule.websockets = this; - WorldAnalysisModules.Singleton.modules.Add(currentModule); - await SendText(webSocket, "ARF World Analysis Server: You are now registered as a module: " + currentName); + WorldAnalysisConnections.Singleton.modules.Add(currentModule); + SendText($"ARF World Analysis Server: #{ WebSocketControllerInstanceCount } You are now registered as module: { currentName }"); } } - else if (msg.StartsWith("RegisterClient:")) + else if (msg.StartsWith("RegisterClient=")) { registered = true; firstTime = false; - currentName = msg.Split(':')[1]; + currentName = msg.Split('=')[1]; if (currentModule == null && currentClient == null) { currentClient = new Client(); currentClient.name = currentName; currentClient.websockets = this; - WorldAnalysisModules.Singleton.clients.Add(currentClient); - await SendText(webSocket, "ARF World Analysis Server: You are now registered as a client: " + currentName); + WorldAnalysisConnections.Singleton.clients.Add(currentClient); + SendText($"ARF World Analysis Server: #{ WebSocketControllerInstanceCount } You are now registered as client: { currentName }"); } } else { registered = false; - await SendText(webSocket, "ARF World Analysis Server: Cannot register " + msg); + SendText("ARF World Analysis Server: Cannot register " + msg); } return; - #endregion } + #endregion if (registered) { // - // Messages from Analysis modules + // Some admin stuffs // - if (msg.StartsWith("PoseNew:")) + if (msg == "Idle") { - // todo: parse the json string - - // Send the pose to the client(s) - Pose p = new Pose(); - WorldAnalysisModules.Singleton.SendPoseToClients(p); - await SendText(webSocket, "RequestNextPose"); + SendText("Idle"); } - else if (msg.StartsWith("Capabilities of")) // Receive capab. from a module + else if (msg == "Busy") { - // Client is sending its capabilities - string[] str_cap = msg.Split('='); - Capability _c = JsonConvert.DeserializeObject<Capability>(str_cap[1]); - if (currentModule != null) currentModule.capabilities.Add(_c); + SendText("Busy"); + } + else if (msg.StartsWith("UnregisterModule=")) + { + string name = msg.Split('=')[1]; + + // Unregister a client (e.g. Unity client) + Module module = null; + foreach (var item in WorldAnalysisConnections.Singleton.modules) if (item.name == name) module = item; + if (module != null) + { + WorldAnalysisConnections.Singleton.modules.Remove(module); + SendText("UnregisterModuleOK"); + } + currentName = ""; + firstTime = true; + registered = false; } + else if (msg.StartsWith("UnregisterClient=")) + { + string name = msg.Split('=')[1]; + // Unregister a client (e.g. Unity client) + Client client = null; + foreach (var item in WorldAnalysisConnections.Singleton.clients) if (item.name == name) client = item; + if (client != null) + { + WorldAnalysisConnections.Singleton.clients.Remove(client); + SendText("UnregisterClientOK"); + } + currentName = ""; + firstTime = true; + registered = false; + } // - // Messages from Unity clients + // OpenAPI // - else if (msg.StartsWith("PoseStart")) // send some fake poses to Unity clients + else if (msg == "PoseIsNowSubscribed") { - await SendDemoPose(webSocket); + SendText("RequestNextPose"); } // - // Messages from modules and clients + // Messages from a module (Analysis) // - else if (msg.StartsWith("TimeStart")) // send time to modules/clients + else if (msg.StartsWith("Capabilities=")) // Receive capab. from a module { - if (msg.Contains(':')) timeCnt = int.Parse(msg.Split(':')[1]); - else timeCnt = 3; - await SendTime(webSocket); + // Module is sending their capabilities + string[] str_cap = msg.Split('='); + Capability _c = JsonConvert.DeserializeObject<Capability>(str_cap[1]); + if (currentModule != null) currentModule.capabilities.Add(_c); } - else if (msg == "UnregisterClient") + else if (msg.StartsWith("NewPose=")) { - // Unregister a client (e.g. Unity client) - currentName = ""; - firstTime = true; - registered = false; - await SendText(webSocket, "SessionStop"); + string[] str_pose = msg.Split('='); + + // Send the pose to the client(s) + Pose pose = JsonConvert.DeserializeObject<Pose>(str_pose[1]); + + //WorldAnalysisConnections.Singleton.SendPoseToClients(pose); + WorldAnalysisConnections.Singleton.SendPoseToClients(str_pose[1]); + + // todo: if there are some subscription ask modules for new pose + SendText("RequestNextPose"); } - else if (msg == "PoseIsRegistered") + else if (msg.StartsWith("Error=")) { - await SendText(webSocket, "RequestNextPose"); + string errorMsg = msg.Split('=')[1]; + foreach (var item in WorldAnalysisConnections.Singleton.clients) + { + await SendText(item.websockets.websocket, errorMsg); + } } - else if (msg == "Idle") + + // + // Messages from a client (Unity) + // + else if (msg.StartsWith("PoseStart")) // send some fake poses to Unity clients { - await SendText(webSocket, "Idle"); + await SendDemoPose(websocket); } - else if (msg == "Busy") + else if (msg.StartsWith("CurrentUserPose=")) { - await SendText(webSocket, "Busy"); + // A client (Unity) is sending the current user posistion + + // Send new user pose to all modules + foreach (var item in WorldAnalysisConnections.Singleton.modules) + { + await SendText(item.websockets.websocket, msg); + } + + //// Test: send user pose back to clients + //string[] str_pose = msg.Split('='); + //Pose pose = JsonConvert.DeserializeObject<Pose>(str_pose[1]); + //pose.Value.Position[2] += 1f; + //WorldAnalysisConnections.Singleton.SendPoseToClients(pose); } + + // + // Messages from modules and clients + // else { // Send a response - await SendText(webSocket, "ARF World Analysis Server: I got this unknown message: " + msg); + SendText("ARF World Analysis Server: I got this unknown message: " + msg); } } } - private async Task HandleClientData(HttpContext context, WebSocket webSocket) + private async Task WebSocketServer_Loop(HttpContext context, WebSocket ws) { + websocket = ws; + WebSocketControllerInstanceCount++; + var buffer = new byte[1024 * 4]; - websockets = webSocket; // Read/get the first data block - WebSocketReceiveResult result = await webSocket.ReceiveAsync(new ArraySegment<byte>(buffer), CancellationToken.None); + WebSocketReceiveResult result = await ws.ReceiveAsync(new ArraySegment<byte>(buffer), CancellationToken.None); if (result.MessageType == WebSocketMessageType.Text) { string getMsg = System.Text.Encoding.UTF8.GetString(buffer, 0, result.Count); - OnReceiveText(webSocket, getMsg); + OnReceiveText(getMsg); } // Entering the loop while (!result.CloseStatus.HasValue) { // Read/get the next block - result = await webSocket.ReceiveAsync(new ArraySegment<byte>(buffer), CancellationToken.None); + result = await ws.ReceiveAsync(new ArraySegment<byte>(buffer), CancellationToken.None); if (result.MessageType == WebSocketMessageType.Text) { string getMsg = System.Text.Encoding.UTF8.GetString(buffer, 0, result.Count); - OnReceiveText(webSocket, getMsg); + OnReceiveText(getMsg); } } - await webSocket.CloseAsync(result.CloseStatus.Value, result.CloseStatusDescription, CancellationToken.None); + await ws.CloseAsync(result.CloseStatus.Value, result.CloseStatusDescription, CancellationToken.None); + WebSocketControllerInstanceCount--; } diff --git a/server/worldanalysis/src/ETSI.ARF.OpenAPI.WorldAnalysis/ETSI-ARF/WorldAnalysisModules.cs b/server/worldanalysis/src/ETSI.ARF.OpenAPI.WorldAnalysis/ETSI-ARF/WorldAnalysisConnections.cs similarity index 57% rename from server/worldanalysis/src/ETSI.ARF.OpenAPI.WorldAnalysis/ETSI-ARF/WorldAnalysisModules.cs rename to server/worldanalysis/src/ETSI.ARF.OpenAPI.WorldAnalysis/ETSI-ARF/WorldAnalysisConnections.cs index 6ec1f5606f492ecc484ee591cd3e9d252d6b64ef..c864547b3750de69a96ae41035c7a6f3c4788622 100644 --- a/server/worldanalysis/src/ETSI.ARF.OpenAPI.WorldAnalysis/ETSI-ARF/WorldAnalysisModules.cs +++ b/server/worldanalysis/src/ETSI.ARF.OpenAPI.WorldAnalysis/ETSI-ARF/WorldAnalysisConnections.cs @@ -17,20 +17,21 @@ namespace ETSI.ARF.OpenAPI.WorldAnalysis public WebSocketController websockets; } - // For management of WA clinet (e.g. Unity client) + // For management of WA client (e.g. Unity client) public class Client { public string name; public string session; + public VectorQuaternionPoseValue currentUserPose; public WebSocketController websockets; } - public class WorldAnalysisModules + public class WorldAnalysisConnections { - static public WorldAnalysisModules Singleton = new WorldAnalysisModules(); + static public WorldAnalysisConnections Singleton = new WorldAnalysisConnections(); public List<Module> modules = new List<Module>(); - public List<Client> clients = new List<Client>(); + public List<Client> clients = new List<Client>(); // e.g. Unity clients // todo // Manage the sessions and modules @@ -58,13 +59,24 @@ namespace ETSI.ARF.OpenAPI.WorldAnalysis return list; } - public void ConfigureFramerate(PoseConfiguration poseConfiguration) + public bool ConfigureFramerate(PoseConfiguration poseConfiguration) { - foreach (var item in modules) + bool ok = false; + foreach (var m in modules) { - // todo: configure the module via websocket - item.websockets.SendText("ConfigureFramerate:" + poseConfiguration.ToJson()); + bool moduleHasCap = false; + foreach (var c in m.capabilities) + { + if ((int)poseConfiguration.TrackableType == (int)c.TrackableType) + { + moduleHasCap = true; + break; + } + } + // Configure the module via websocket + if (moduleHasCap) m.websockets.SendText("ConfigureFramerate=" + poseConfiguration.ToJson()); } + return ok; } public Pose GetPose(Guid trackableOrAnchorUUID, ModeWorldAnalysis mode) @@ -73,39 +85,48 @@ namespace ETSI.ARF.OpenAPI.WorldAnalysis foreach (var item in modules) { // todo: get the pose via websocket - item.websockets.SendText("GetPose:" + trackableOrAnchorUUID + ":" + mode.ToString()); + item.websockets.SendText("GetPose=" + trackableOrAnchorUUID + "=" + mode.ToString()); // todo: find the pose with the best confidence? // How to get the results !?!?! (List with results, per Modules) } return result; } - - - public void SubscribeToPose(SubscriptionInfo info) + + public void SubscribeToPose(SubscriptionInfo info, Guid parentTrackableUUID) { // Send to all modules a request of subscription foreach (var item in modules) { - item.websockets.SendText("SubscribePose:" + info.uuidTarget.ToString()); + if (parentTrackableUUID == Guid.Empty) item.websockets.SendText("SubscribePose=" + info.uuidSub + "=" + info.subscription.ToJson()); + else item.websockets.SendText("SubscribeSimulatedPose=" + info.uuidSub + "=" + info.subscription.ToJson() + "=" + parentTrackableUUID.ToString()); } } - public void UnsubscribeFromPose(Guid uuid) + public void UnsubscribeFromPose(Guid subscriptionUUID) { // Send to all modules a request of subscription foreach (var item in modules) { - item.websockets.SendText("UnsubscribePose:" + uuid.ToString()); + item.websockets.SendText("UnsubscribePose=" + subscriptionUUID.ToString()); + } + } + + public void SendPoseToClients(Pose pose) + { + // Send to all clients with valid subscription the new pose + foreach (var item in clients) + { + item.websockets.SendText("NewPose=" + pose.ToJson()); } } - public void SendPoseToClients(Pose p) + public void SendPoseToClients(string str_pose) { // Send to all clients with valid subscription the new pose foreach (var item in clients) { - item.websockets.SendText("Pose=" + p.ToJson()); + item.websockets.SendText("NewPose=" + str_pose); } } } diff --git a/server/worldanalysis/src/ETSI.ARF.OpenAPI.WorldAnalysis/Startup.cs b/server/worldanalysis/src/ETSI.ARF.OpenAPI.WorldAnalysis/Startup.cs index f56fad2d4b07afcd683ccc65a18b464cac76890b..2a3f67c8c81faf23840f8f5e81c90a997d5cb9ba 100644 --- a/server/worldanalysis/src/ETSI.ARF.OpenAPI.WorldAnalysis/Startup.cs +++ b/server/worldanalysis/src/ETSI.ARF.OpenAPI.WorldAnalysis/Startup.cs @@ -15,7 +15,7 @@ // See the License for the specific language governing permissions and // limitations under the License. // -// Last change: June 2024 +// Last change: September 2024 // /* @@ -23,7 +23,7 @@ * * API ensuring interoperability between Scene Management and a World Analysis service * - * The version of the OpenAPI document: 2.0.1 + * The version of the OpenAPI document: 2.0.2 * * Generated by: https://openapi-generator.tech */ @@ -64,17 +64,18 @@ namespace ETSI.ARF.OpenAPI.WorldAnalysis /// <summary> /// The API version. (how to read it from the yaml?) /// </summary> - static public string apiVersion = "2.0.1"; + static public string apiVersion = "2.0.0"; /// <summary> /// Demo access key /// </summary> - static public string accessKey = "My!Key.ETSI"; + static public string accessKey = "ARF"; /// <summary> /// Demo secret key /// </summary> - static public string secretKey = "GW0Wae1t4Cs5rAqEbPYFWO9J5nSbpJXxp1F3uv0J"; + //static public string secretKey = "GW0Wae1t4Cs5rAqEbPYFWO9J5nSbpJXxp1F3uv0J"; + static public string secretKey = "hhi"; /// <summary> /// Constructor @@ -90,6 +91,20 @@ namespace ETSI.ARF.OpenAPI.WorldAnalysis /// </summary> public IConfiguration Configuration { get; } + /// <summary> + /// SylR: Check if the request is authorized + /// </summary> + /// <param name="token"></param> + /// <returns></returns> + static public bool IsAccessGranted(string token) + { + if (token == null) return false; + else if (token == "dev") return true; // developermode + + string[] t = token.Split('&'); + return t[0] == accessKey && t[1] == secretKey; + } + /// <summary> /// This method gets called by the runtime. Use this method to add services to the container. /// </summary> @@ -116,7 +131,7 @@ namespace ETSI.ARF.OpenAPI.WorldAnalysis { c.EnableAnnotations(enableAnnotationsForInheritance: true, enableAnnotationsForPolymorphism: true); - c.SwaggerDoc("2.0.1", new OpenApiInfo + c.SwaggerDoc(apiVersion, new OpenApiInfo { Title = "World Analysis API", Description = "World Analysis API (ASP.NET Core 5.0)", @@ -132,7 +147,7 @@ namespace ETSI.ARF.OpenAPI.WorldAnalysis Name = "NoLicense", Url = new Uri("https://opensource.org/licenses/BSD-3-Clause") }, - Version = "2.0.1", + Version = apiVersion, }); c.CustomSchemaIds(type => type.FriendlyId(true)); c.IncludeXmlComments($"{AppContext.BaseDirectory}{Path.DirectorySeparatorChar}{Assembly.GetEntryAssembly().GetName().Name}.xml"); @@ -183,7 +198,7 @@ namespace ETSI.ARF.OpenAPI.WorldAnalysis // set route prefix to openapi, e.g. http://localhost:8080/openapi/index.html c.RoutePrefix = "openapi"; //TODO: Either use the SwaggerGen generated OpenAPI contract (generated from C# classes) - c.SwaggerEndpoint("/openapi/2.0.1/openapi.json", "World Analysis API"); + c.SwaggerEndpoint("/openapi/" + apiVersion + "/openapi.json", "World Analysis API"); //TODO: Or alternatively use the original OpenAPI contract that's included in the static files // c.SwaggerEndpoint("/openapi-original.json", "World Analysis API Original");