Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
  • arf/world-analysis-api-helpers/world-analysis-asp.net-server
1 result
Show changes
Commits on Source (7)
Showing
with 645 additions and 67 deletions
openapi @ 073fd721
Subproject commit 073fd7213fd9e6ebc2f8a47d628a650de30c8bc4
#See https://aka.ms/containerfastmode to understand how Visual Studio uses this Dockerfile to build your images for faster debugging.
# Container we use for final publish
FROM mcr.microsoft.com/dotnet/core/aspnet:5.0-buster-slim AS base
FROM mcr.microsoft.com/dotnet/aspnet:5.0-buster-slim AS base
WORKDIR /app
EXPOSE 80
EXPOSE 443
# Build container
FROM mcr.microsoft.com/dotnet/core/sdk:5.0-buster AS build
FROM mcr.microsoft.com/dotnet/sdk:5.0-buster-slim AS build
# Copy the code into the container
WORKDIR /src
COPY ["src/ETSI.ARF.OpenAPI.WorldAnalysis/ETSI.ARF.OpenAPI.WorldAnalysis.csproj", "ETSI.ARF.OpenAPI.WorldAnalysis/"]
COPY ["ETSI.ARF.OpenAPI.WorldAnalysis.csproj", "ETSI.ARF.OpenAPI.WorldAnalysis/"]
# NuGet restore
RUN dotnet restore "ETSI.ARF.OpenAPI.WorldAnalysis/ETSI.ARF.OpenAPI.WorldAnalysis.csproj"
COPY ["src/ETSI.ARF.OpenAPI.WorldAnalysis/", "ETSI.ARF.OpenAPI.WorldAnalysis/"]
COPY [".", "ETSI.ARF.OpenAPI.WorldAnalysis/"]
# Build the API
WORKDIR "ETSI.ARF.OpenAPI.WorldAnalysis"
......
......@@ -53,7 +53,22 @@ namespace ETSI.ARF.OpenAPI.WorldAnalysis.Controllers
/// </summary>
public override IActionResult GetCapabilities([FromHeader (Name = "token")]string token, [FromHeader (Name = "sessionID")]string sessionID)
{
return StatusCode(405, "Not supported yet!");
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(WorldAnalysisConnections.Singleton.GetCapabilities());
// Create response object
GetCapabilities200Response response = new GetCapabilities200Response();
response.Capabilities = capabilitiesList;
return new ObjectResult(response);
//return StatusCode(405, "Not supported yet!");
}
/// <summary>
......@@ -61,7 +76,19 @@ 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)
{
return StatusCode(405, "Not supported yet!");
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(WorldAnalysisConnections.Singleton.GetCapabilitiesFromUuid(trackableOrAnchorUUID));
// Create response object
GetSupport200Response response = new GetSupport200Response();
response.Capabilities = capabilitiesList;
return new ObjectResult(response);
//return StatusCode(405, "Not supported yet!");
}
}
}
......@@ -41,66 +41,212 @@ 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)
{
return StatusCode(405, "Not supported yet!");
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
bool result = WorldAnalysisConnections.Singleton.ConfigureFramerate(poseConfiguration);
return result ? StatusCode(200, "Ok.") : StatusCode(405, "Not supported.");
}
/// <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)
{
return StatusCode(405, "Not supported yet!");
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 = WorldAnalysisConnections.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!");
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(WorldAnalysisConnections.Singleton.GetPose(item.Uuid, item.Mode));
}
return new ObjectResult(result);
}
#pragma warning disable CS1591 // Fehlendes XML-Kommentar fr ffentlich sichtbaren Typ oder Element
//
// Management of subscriptions
//
/// <summary>
/// Dictionnary of susbscription informations for poses, for each item, stored using the UUID of the item (anchor/trackable)
/// </summary>
private Dictionary<Guid, SubscriptionInfo> m_subscriptionsPoses = new Dictionary<Guid, SubscriptionInfo>();
public struct SubscriptionInfo
{
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 fr ffentlich sichtbaren Typ oder Element
/// <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)
{
return StatusCode(405, "Not supported yet!");
if (!Startup.IsAccessGranted(token)) return StatusCode(511, new Error() { Message = "Invalid token!" });
// todo: compare sessionID
// 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>
/// 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)
{
return StatusCode(405, "Not supported yet!");
// 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.uuidSub = response.Uuid;
info.webSocket = response.WebsocketUrl;
info.timeValidity = DateTime.Now.AddMilliseconds(validity / 1000.0f);
info.pose = new Pose();
info.pose.Mode = response.Mode;
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?!?
WorldAnalysisConnections.Singleton.SubscribeToPose(info, parentUUID);
return new ObjectResult(response);
}
/// <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)
{
return StatusCode(405, "Not supported yet!");
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
WorldAnalysisConnections.Singleton.UnsubscribeFromPose(subscriptionUUID);
return StatusCode(200, new Success() { Message = "Unsubscription successfull." });
}
/// <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)
{
if (!Startup.IsAccessGranted(token)) return StatusCode(511, new Error() { Message = "Invalid token!" });
// todo: compare sessionID
// todo: inform the module(s) that the subscription changed
return StatusCode(405, "Not supported yet!");
}
}
......
......@@ -30,9 +30,12 @@ using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Http;
using Swashbuckle.AspNetCore.Annotations;
using Swashbuckle.AspNetCore.SwaggerGen;
using Newtonsoft.Json;
using ETSI.ARF.OpenAPI.WorldAnalysis.Models;
#pragma warning disable CS1591 // Fehlendes XML-Kommentar fr ffentlich sichtbaren Typ oder Element
namespace ETSI.ARF.OpenAPI.WorldStorage.Services
namespace ETSI.ARF.OpenAPI.WorldAnalysis.Controllers
{
//
// ETSI-ARF World Analysis WebSocket implementation
......@@ -40,6 +43,14 @@ namespace ETSI.ARF.OpenAPI.WorldStorage.Services
//
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;
[HttpGet("/ws")]
public async Task Get()
{
......@@ -47,12 +58,15 @@ namespace ETSI.ARF.OpenAPI.WorldStorage.Services
{
using var webSocket = await HttpContext.WebSockets.AcceptWebSocketAsync();
if (webSocket.State == WebSocketState.Open)
if (webSocket.State == WebSocketState.Connecting)
{
// Response an OK message
await SendText(webSocket, "Hello, here is the ARF World Analysis services!");
// Response an OK message?
}
else if (webSocket.State == WebSocketState.Open)
{
// Go to the loop...
await WebSocketServer_Loop(HttpContext, webSocket);
}
await Echo(HttpContext, webSocket);
}
else
{
......@@ -64,59 +78,298 @@ namespace ETSI.ARF.OpenAPI.WorldStorage.Services
//
// 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 msg)
{
var message = msg;
var bytes = Encoding.UTF8.GetBytes(message);
var arraySegment = new ArraySegment<byte>(bytes, 0, bytes.Length);
websocket.SendAsync(arraySegment, WebSocketMessageType.Text, true, CancellationToken.None);
}
//
// Send the time all seconds
// Send a demo pose all seconds
//
private async Task SendTime(WebSocket webSocket)
float rotInc = 0;
private async Task SendDemoPose(WebSocket ws)
{
while (true)
{
var message = "Hello, my time is: " + DateTime.Now.ToLocalTime();
var bytes = Encoding.UTF8.GetBytes(message);
var arraySegment = new ArraySegment<byte>(bytes, 0, bytes.Length);
if (webSocket.State == WebSocketState.Open)
if (ws.State == WebSocketState.Open)
{
await webSocket.SendAsync(arraySegment, WebSocketMessageType.Text, true, CancellationToken.None);
timeCnt--;
if (timeCnt < 0)
{
await SendText(ws, "PoseStop");
break;
}
else
{
PoseValue val = new PoseValue();
val.Unit = UnitSystem.MEnum;
val.Position = new List<float>() { 1, 2, 3 };
val.Rotation = new List<float>() { rotInc, 0, 0, 0 };
rotInc += 0.01f;
Pose pose = new Pose();
pose.Uuid = new Guid();
pose.EstimationState = Pose.EstimationStateEnum.OKEnum;
//pose.SubscriptionUrl = "";
pose.Value = val;
pose.Timestamp = (int)DateTime.Now.ToFileTime();
pose.Confidence = 1;
pose.Mode = ModeWorldAnalysis.DEVICETOTRACKABLESEnum;
string json = pose.ToJson();
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 (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;
}
Thread.Sleep(1000);
Thread.Sleep(250);
}
}
private Module currentModule;
private Client currentClient;
private async void OnReceiveText(string msg)
{
#region Register the client/module
if (firstTime)
{
if (msg.StartsWith("RegisterModule="))
{
registered = true;
firstTime = false;
currentName = msg.Split('=')[1];
// If module exist already reuse it
Module module = null;
foreach (var item in WorldAnalysisConnections.Singleton.modules) if (item.name == currentName) module = item;
if (module != null)
{
currentModule = module;
currentModule.name = currentName;
currentModule.websockets = this;
currentModule.capabilities.Clear();
}
if (currentModule == null && currentClient == null)
{
currentModule = new Module();
currentModule.name = currentName;
currentModule.websockets = this;
WorldAnalysisConnections.Singleton.modules.Add(currentModule);
}
SendText($"ARF World Analysis Server: #{ WebSocketControllerInstanceCount } You are now registered as module: { currentName }");
}
else if (msg.StartsWith("RegisterClient="))
{
registered = true;
firstTime = false;
currentName = msg.Split('=')[1];
if (currentModule == null && currentClient == null)
{
currentClient = new Client();
currentClient.name = currentName;
currentClient.websockets = this;
WorldAnalysisConnections.Singleton.clients.Add(currentClient);
SendText($"ARF World Analysis Server: #{ WebSocketControllerInstanceCount } You are now registered as client: { currentName }");
}
}
else
{
registered = false;
SendText("ARF World Analysis Server: Cannot register " + msg);
}
return;
}
#endregion
if (registered)
{
//
// Some admin stuffs
//
if (msg == "Idle")
{
SendText("Idle");
}
else if (msg == "Busy")
{
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;
}
//
// OpenAPI
//
else if (msg == "PoseIsNowSubscribed")
{
SendText("RequestNextPose");
}
//
// Messages from a module (Analysis)
//
else if (msg.StartsWith("Capabilities=")) // Receive capab. from a module
{
// Module is sending their capabilities
string[] str_cap = msg.Split('=');
string moduleName = str_cap[1];
Capability _c = JsonConvert.DeserializeObject<Capability>(str_cap[2]);
// Has the module already send their capabilities?
Module module = null;
foreach (var item in WorldAnalysisConnections.Singleton.modules) if (item.name == moduleName) module = item;
if (module != null) module.capabilities.Add(_c);
}
else if (msg.StartsWith("NewPose="))
{
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.StartsWith("Error="))
{
string errorMsg = msg.Split('=')[1];
foreach (var item in WorldAnalysisConnections.Singleton.clients)
{
await SendText(item.websockets.websocket, errorMsg);
}
}
//
// Messages from a client (Unity)
//
else if (msg.StartsWith("PoseStart")) // send some fake poses to Unity clients
{
await SendDemoPose(websocket);
}
else if (msg.StartsWith("CurrentUserPose="))
{
// 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
SendText("ARF World Analysis Server: I got this unknown message: " + msg);
}
}
}
private async Task WebSocketServer_Loop(HttpContext context, WebSocket ws)
{
websocket = ws;
WebSocketControllerInstanceCount++;
var buffer = new byte[1024 * 4];
// Read/get the first data block
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(getMsg);
}
// Entering the loop
while (!result.CloseStatus.HasValue)
{
// Read/get the next block
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(getMsg);
}
}
await ws.CloseAsync(result.CloseStatus.Value, result.CloseStatusDescription, CancellationToken.None);
WebSocketControllerInstanceCount--;
}
//
// Echo incoming messages
//
private async Task Echo(HttpContext context, WebSocket webSocket)
private async Task Echo(WebSocketReceiveResult context, WebSocket webSocket)
{
var buffer = new byte[1024 * 4];
// get the first data block
// Read/get the first data block
WebSocketReceiveResult result = await webSocket.ReceiveAsync(new ArraySegment<byte>(buffer), CancellationToken.None);
while (!result.CloseStatus.HasValue)
{
// test
await SendText(webSocket, "Thanks, I got this message:");
// echo the message back to the client
await webSocket.SendAsync(new ArraySegment<byte>(buffer, 0, result.Count), result.MessageType, result.EndOfMessage, CancellationToken.None);
// get the next block
result = await webSocket.ReceiveAsync(new ArraySegment<byte>(buffer), CancellationToken.None);
}
await webSocket.CloseAsync(result.CloseStatus.Value, result.CloseStatusDescription, CancellationToken.None);
// Send/echo the message back to the client
await SendText(webSocket, "ARF World Analysis Server: I got this (raw) message: ");
await webSocket.SendAsync(new ArraySegment<byte>(buffer, 0, result.Count), result.MessageType, result.EndOfMessage, CancellationToken.None);
}
}
}
......
using ETSI.ARF.OpenAPI.WorldAnalysis.Models;
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
{
// For management of WA modules
public class Module
{
public string name;
public List<Capability> capabilities = new List<Capability>();
public WebSocketController websockets;
}
// 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 WorldAnalysisConnections
{
static public WorldAnalysisConnections Singleton = new WorldAnalysisConnections();
public List<Module> modules = new List<Module>();
public List<Client> clients = new List<Client>(); // e.g. Unity clients
// todo
// Manage the sessions and modules
// get capabilities of all modules
public List<Capability> GetCapabilities()
{
List<Capability> list = new List<Capability>();
foreach (var item in modules)
{
list.AddRange(item.capabilities);
}
return list;
}
public List<Capability> GetCapabilitiesFromUuid(Guid trackableOrAnchorUUID)
{
List<Capability> list = new List<Capability>();
foreach (var item in modules)
{
// todo: Check if uuid has the capability?
// Get the world object from the world storage via the module?
// Use GetRelocalisation() ?
list.AddRange(item.capabilities);
}
return list;
}
public bool ConfigureFramerate(PoseConfiguration poseConfiguration)
{
bool ok = false;
foreach (var m in modules)
{
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)
{
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, Guid parentTrackableUUID)
{
// Send to all modules a request of subscription
foreach (var item in modules)
{
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 subscriptionUUID)
{
// Send to all modules a request of subscription
foreach (var item in modules)
{
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(string str_pose)
{
// Send to all clients with valid subscription the new pose
foreach (var item in clients)
{
item.websockets.SendText("NewPose=" + str_pose);
}
}
}
}
#pragma warning restore CS1591 // Fehlendes XML-Kommentar für öffentlich sichtbaren Typ oder Element
......@@ -27,7 +27,7 @@ namespace ETSI.ARF.OpenAPI.WorldAnalysis
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>()
.UseUrls("http://0.0.0.0:8080/");
.UseUrls("http://0.0.0.0:44301/"); // SylR: Wichtig!!!
});
}
}
......@@ -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");
......@@ -161,14 +176,15 @@ namespace ETSI.ARF.OpenAPI.WorldAnalysis
app.UseHsts();
}
// ETSI-ARF Websocket implementation
// ETSI-ARF Websockets implementation
var webSocketOptions = new WebSocketOptions()
{
KeepAliveInterval = TimeSpan.FromSeconds(120),
//KeepAliveInterval = TimeSpan.FromSeconds(120),
//AllowedOrigins.Add("https://etsi.hhi.fraunhofer.de"),
//AllowedOrigins.Add("https://www.client.com")
};
//webSocketOptions.AllowedOrigins.Add("https://etsi.hhi.fraunhofer.de");
//webSocketOptions.AllowedOrigins.Add("https://www.client.com");
app.UseWebSockets();
app.UseWebSockets(webSocketOptions);
//app.UseWebSockets();
app.UseHttpsRedirection();
app.UseDefaultFiles();
......@@ -182,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");
......
......@@ -71,6 +71,7 @@
var port = document.location.port ? (":" + document.location.port) : "";
connectionUrl.value = scheme + "://" + document.location.hostname + port + "/ws" ;
//connectionUrl.value = scheme + "://" + "etsi.hhi.fraunhofer.de" + "/ws" ;
function updateState() {
function disable() {
......