diff --git a/openapi b/openapi index 073fd7213fd9e6ebc2f8a47d628a650de30c8bc4..b639a02180c2b5e301c77483b3a2fa645ba94169 160000 --- a/openapi +++ b/openapi @@ -1 +1 @@ -Subproject commit 073fd7213fd9e6ebc2f8a47d628a650de30c8bc4 +Subproject commit b639a02180c2b5e301c77483b3a2fa645ba94169 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 2efde9582b63456818e9cdf7a4580eaace02aecc..058e38806c8618f1b8a64de2238996e8eef5c7ac 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 @@ -41,39 +41,52 @@ using ETSI.ARF.OpenAPI.WorldAnalysis.Attributes; using ETSI.ARF.OpenAPI.WorldAnalysis.Models; namespace ETSI.ARF.OpenAPI.WorldAnalysis.Controllers -{ +{ /// <summary> /// /// </summary> [ApiController] public class PoseApiControlleImpl : PoseApiController - { + { /// <summary> /// Specify the a minimum frame rate for pose estimation for Trackable types /// </summary> - public override IActionResult ConfigureFramerate([FromBody]PoseConfiguration poseConfiguration, [FromHeader (Name = "token")]string token, [FromHeader (Name = "sessionID")]string sessionID) + public override IActionResult ConfigureFramerate([FromBody] PoseConfiguration poseConfiguration, [FromHeader(Name = "token")] string token, [FromHeader(Name = "sessionID")] string sessionID) { // todo: compare token and sessionID - return StatusCode(405, "Not supported yet!"); + // Notify the modules that the client need a new framerate + WorldAnalysisModules.Singleton.ConfigureFramerate(poseConfiguration); + return StatusCode(200, "Ok."); } /// <summary> /// Request the last pose of a single Anchor or Trackable /// </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) + 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 - return StatusCode(405, "Not supported yet!"); + // Request from the modules a new pose of a single UUID + Pose result; + result = WorldAnalysisModules.Singleton.GetPose(trackableOrAnchorUUID, mode); + return new ObjectResult(result); } /// <summary> /// Request the last pose of a batch of Anchor or Trackable /// </summary> - public override IActionResult GetPoses([FromQuery (Name = "uuid")][Required()]List<GetPosesUuidParameterInner> uuid, [FromHeader (Name = "token")]string token, [FromHeader (Name = "sessionID")]string sessionID) + public override IActionResult GetPoses([FromQuery(Name = "uuid")][Required()] List<UuidAndMode> uuid, [FromHeader(Name = "token")] string token, [FromHeader(Name = "sessionID")] string sessionID) { - return StatusCode(405, "Not supported yet!"); + // todo: compare token and 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)); + } + return new ObjectResult(result); } #pragma warning disable CS1591 // Fehlendes XML-Kommentar für öffentlich sichtbaren Typ oder Element @@ -99,42 +112,53 @@ namespace ETSI.ARF.OpenAPI.WorldAnalysis.Controllers /// <summary> /// Get information about a subscription /// </summary> - public override IActionResult GetSubscription([FromRoute (Name = "subscriptionUUID")][Required]Guid subscriptionUUID, [FromHeader (Name = "token")]string token, [FromHeader (Name = "sessionID")]string sessionID) + 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 + // todo: search for a subscription and send it back + return StatusCode(405, "Not supported yet!"); } /// <summary> /// Subscribe to collect the pose of an AR device, an Anchor or a Trackable /// </summary> - public override IActionResult SubscribeToPose([FromBody]SubscribeToPoseRequest subscribeToPoseRequest, [FromHeader (Name = "token")]string token, [FromHeader (Name = "sessionID")]string sessionID) + public override IActionResult SubscribeToPose([FromBody] SubscribeToPoseRequest subscribeToPoseRequest, [FromHeader(Name = "token")] string token, [FromHeader(Name = "sessionID")] string sessionID) { // todo: compare token and sessionID int validity = subscribeToPoseRequest.Validity; // todo: is to handle here or by the client? // We add the subscription - SubscriptionInfo sub = new SubscriptionInfo(); - sub.uuid = new Guid(); - sub.timeValidity = DateTime.Now.AddMilliseconds(validity / 1000.0f); - sub.pose = new Pose(); - sub.pose.Mode = subscribeToPoseRequest.Mode[0]; - sub.uuidTarget = subscribeToPoseRequest.Target; - m_subscriptionsPoses.Add(sub.uuid, sub); + SubscriptionInfo info = new SubscriptionInfo(); + info.uuid = new Guid(); + info.uuidTarget = subscribeToPoseRequest.Target; + info.timeValidity = DateTime.Now.AddMilliseconds(validity / 1000.0f); + info.pose = new Pose(); + info.pose.Mode = subscribeToPoseRequest.Mode; + m_subscriptionsPoses.Add(info.uuid, info); + + // todo: handle multiple subscriptions ? + // subscribeToPoseRequest.Targets[] + // subscribeToPoseRequest.Modes[] + + // 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.Uuid = sub.uuid; response.Validity = validity; - response.Target = sub.uuidTarget; - response.Mode = sub.pose.Mode; + 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" - + // todo: register the client, so the websocket will send to it pose updates } else @@ -143,26 +167,33 @@ namespace ETSI.ARF.OpenAPI.WorldAnalysis.Controllers response.WebsocketUrl = null; } return new ObjectResult(response); - //return StatusCode(405, "Not supported yet!"); } /// <summary> /// Remove a subscription to a given pose /// </summary> - public override IActionResult UnsubscribeFromPose([FromRoute (Name = "subscriptionUUID")][Required]Guid subscriptionUUID, [FromHeader (Name = "token")]string token, [FromHeader (Name = "sessionID")]string sessionID) + 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 - return StatusCode(405, "Not supported yet!"); + // Remove the subscription from the list? + if (m_subscriptionsPoses.ContainsKey(subscriptionUUID)) m_subscriptionsPoses.Remove(subscriptionUUID); + + // Inform the module(s) that the subscription ended + WorldAnalysisModules.Singleton.UnsubscribeFromPose(subscriptionUUID); + + return StatusCode(200, "Ok!"); } /// <summary> /// Update a subscription /// </summary> - public override IActionResult UpdateSubscription([FromRoute (Name = "subscriptionUUID")][Required]Guid subscriptionUUID, [FromBody]UpdateSubscriptionRequest updateSubscriptionRequest, [FromHeader (Name = "token")]string token, [FromHeader (Name = "sessionID")]string sessionID) + 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 + // todo: inform the module(s) that the subscription changed + return StatusCode(405, "Not supported yet!"); } } 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 072271569df7a96b2b514511021fb4edc570cd96..8a76568e404ccd1c8b622f84521d46a4453f5327 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 @@ -47,6 +47,7 @@ namespace ETSI.ARF.OpenAPI.WorldAnalysis.Controllers private bool registered = false; private bool firstTime = true; private int timeCnt = 3; + private WebSocket websockets; [HttpGet("/ws")] public async Task Get() @@ -83,6 +84,15 @@ namespace ETSI.ARF.OpenAPI.WorldAnalysis.Controllers await webSocket.SendAsync(arraySegment, WebSocketMessageType.Text, true, CancellationToken.None); } + public void SendText(string text) + { + // Response an OK message + var message = text; + var bytes = Encoding.UTF8.GetBytes(message); + var arraySegment = new ArraySegment<byte>(bytes, 0, bytes.Length); + websockets.SendAsync(arraySegment, WebSocketMessageType.Text, true, CancellationToken.None); + } + // // Send the time all seconds // @@ -91,7 +101,7 @@ namespace ETSI.ARF.OpenAPI.WorldAnalysis.Controllers { while (true) { - var message = "ARF World Analysis Server, Time = " + DateTime.Now.ToLocalTime(); + var message = "Time=" + DateTime.Now.ToLocalTime(); if (webSocket.State == WebSocketState.Open) { @@ -112,7 +122,7 @@ namespace ETSI.ARF.OpenAPI.WorldAnalysis.Controllers } float rotInc = 0; - private async Task SendPose(WebSocket webSocket) + private async Task SendDemoPose(WebSocket webSocket) { while (true) { @@ -143,7 +153,7 @@ namespace ETSI.ARF.OpenAPI.WorldAnalysis.Controllers string json = pose.ToJson(); - await SendText(webSocket, json); + await SendText(webSocket, "Pose=" + json); } } else if (webSocket.State == WebSocketState.Closed || webSocket.State == WebSocketState.Aborted) @@ -165,7 +175,7 @@ namespace ETSI.ARF.OpenAPI.WorldAnalysis.Controllers { if (firstTime) { - // Register the client/module + #region Register the client/module if (msg.StartsWith("RegisterModule:")) { registered = true; @@ -175,6 +185,7 @@ namespace ETSI.ARF.OpenAPI.WorldAnalysis.Controllers { 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); } @@ -188,6 +199,7 @@ namespace ETSI.ARF.OpenAPI.WorldAnalysis.Controllers { 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); } @@ -197,36 +209,61 @@ namespace ETSI.ARF.OpenAPI.WorldAnalysis.Controllers registered = false; await SendText(webSocket, "ARF World Analysis Server: Cannot register " + msg); } + return; + #endregion } - else if (registered) + + if (registered) { - if (msg.StartsWith("PoseStart")) + // + // Messages from Analysis modules + // + if (msg.StartsWith("PoseNew:")) { - if (msg.Contains(':')) timeCnt = int.Parse(msg.Split(':')[1]); - else timeCnt = 3; - await SendPose(webSocket); - } - else if (msg.StartsWith("TimeStart")) - { - if (msg.Contains(':')) timeCnt = int.Parse(msg.Split(':')[1]); - else timeCnt = 3; - await SendTime(webSocket); + // todo: parse the json string + + // Send the pose to the client(s) + Pose p = new Pose(); + WorldAnalysisModules.Singleton.SendPoseToClients(p); + await SendText(webSocket, "RequestNextPose"); } - else if (msg.StartsWith("Capabilities")) + else if (msg.StartsWith("Capabilities of")) // Receive capab. from a module { // 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); } + + // + // Messages from Unity clients + // + else if (msg.StartsWith("PoseStart")) // send some fake poses to Unity clients + { + await SendDemoPose(webSocket); + } + + // + // Messages from modules and clients + // + else if (msg.StartsWith("TimeStart")) // send time to modules/clients + { + if (msg.Contains(':')) timeCnt = int.Parse(msg.Split(':')[1]); + else timeCnt = 3; + await SendTime(webSocket); + } else if (msg == "UnregisterClient") { - // Unregister client/user + // Unregister a client (e.g. Unity client) currentName = ""; firstTime = true; registered = false; await SendText(webSocket, "SessionStop"); } + else if (msg == "PoseIsRegistered") + { + await SendText(webSocket, "RequestNextPose"); + } else if (msg == "Idle") { await SendText(webSocket, "Idle"); @@ -246,6 +283,7 @@ namespace ETSI.ARF.OpenAPI.WorldAnalysis.Controllers private async Task HandleClientData(HttpContext context, WebSocket webSocket) { 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); diff --git a/server/worldanalysis/src/ETSI.ARF.OpenAPI.WorldAnalysis/ETSI-ARF/WorldAnalysisModules.cs b/server/worldanalysis/src/ETSI.ARF.OpenAPI.WorldAnalysis/ETSI-ARF/WorldAnalysisModules.cs index f4b3938d658a8f2a0852013ba111832b9412611a..6ec1f5606f492ecc484ee591cd3e9d252d6b64ef 100644 --- a/server/worldanalysis/src/ETSI.ARF.OpenAPI.WorldAnalysis/ETSI-ARF/WorldAnalysisModules.cs +++ b/server/worldanalysis/src/ETSI.ARF.OpenAPI.WorldAnalysis/ETSI-ARF/WorldAnalysisModules.cs @@ -3,6 +3,8 @@ using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; +using static ETSI.ARF.OpenAPI.WorldAnalysis.Controllers.PoseApiControlleImpl; +using ETSI.ARF.OpenAPI.WorldAnalysis.Controllers; #pragma warning disable CS1591 // Fehlendes XML-Kommentar für öffentlich sichtbaren Typ oder Element namespace ETSI.ARF.OpenAPI.WorldAnalysis @@ -12,6 +14,7 @@ namespace ETSI.ARF.OpenAPI.WorldAnalysis { public string name; public List<Capability> capabilities = new List<Capability>(); + public WebSocketController websockets; } // For management of WA clinet (e.g. Unity client) @@ -19,7 +22,7 @@ namespace ETSI.ARF.OpenAPI.WorldAnalysis { public string name; public string session; - + public WebSocketController websockets; } public class WorldAnalysisModules @@ -48,12 +51,63 @@ namespace ETSI.ARF.OpenAPI.WorldAnalysis foreach (var item in modules) { // todo: Check if uuid has the capability? - // Get the world object from the storage via the module? - + // Get the world object from the world storage via the module? + // Use GetRelocalisation() ? list.AddRange(item.capabilities); } return list; } + + public void ConfigureFramerate(PoseConfiguration poseConfiguration) + { + foreach (var item in modules) + { + // todo: configure the module via websocket + item.websockets.SendText("ConfigureFramerate:" + poseConfiguration.ToJson()); + } + } + + public Pose GetPose(Guid trackableOrAnchorUUID, ModeWorldAnalysis mode) + { + Pose result = new Pose(); + foreach (var item in modules) + { + // todo: get the pose via websocket + 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) + { + // Send to all modules a request of subscription + foreach (var item in modules) + { + item.websockets.SendText("SubscribePose:" + info.uuidTarget.ToString()); + } + } + + public void UnsubscribeFromPose(Guid uuid) + { + // Send to all modules a request of subscription + foreach (var item in modules) + { + item.websockets.SendText("UnsubscribePose:" + uuid.ToString()); + } + } + + public void SendPoseToClients(Pose p) + { + // Send to all clients with valid subscription the new pose + foreach (var item in clients) + { + item.websockets.SendText("Pose=" + p.ToJson()); + } + } } } #pragma warning restore CS1591 // Fehlendes XML-Kommentar für öffentlich sichtbaren Typ oder Element