Skip to content
Snippets Groups Projects
Commit 24e858d2 authored by Sylvain Renault's avatar Sylvain Renault
Browse files

API draft implementation and integration of a websockets client.

parent cafd4e6c
No related branches found
No related tags found
1 merge request!2Finalisation of REST and websocket client functions and subscription methodics.
This commit is part of merge request !2. Comments created here will be created in the context of that merge request.
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using ETSI.ARF.OpenAPI.WorldAnalysis;
using ETSI.ARF.WorldAnalysis;
using static WorldAnalysisInterface;
using ETSI.ARF.WorldAnalysis.REST;
using WebSocketSharp;
//Implementation of the WorldAnalysis interface
public class WorldAnalysisREST : MonoBehaviour, WorldAnalysisInterface
{
static protected string token = "ARF_Permission";
//
// Inspector variables
//
/// <summary>
/// WorldSAnalysisServer
/// </summary>
public WorldAnalysisServer waServer;
public string token = "ETSI-ARF-STF";
public string sessionID = "RESTful-API";
// For sync calls
private WorldAnalysisClient apiClient;
[Space(8)]
public bool isDebug = false;
// For async calls
private WorldAnalysisClient apiClientAsync;
//
// Private members
//
private WorldAnalysisClient apiClient; // For sync calls
private WorldAnalysisClient apiClientAsync; // For async calls
private WebSocketSharp.WebSocket webSocket; // For WebSockets
private bool websocketConnected = false;
//
// 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;
public struct SubscriptionInfo
{
public Guid uuid; // id of subscription (id is defined by the WA server)
public Guid uuidTarget; // id trackable or anchor
public float timeValidity; //The duration of the validity of the subscription
public ETSI.ARF.OpenAPI.WorldAnalysis.Pose pose;
public PoseCallback callback;
}
#region Unity_Methods
......@@ -26,6 +57,11 @@ public class WorldAnalysisREST : MonoBehaviour, WorldAnalysisInterface
/// </summary>
protected void Awake()
{
Instance = this;
//m_relocalizationInformations = new Dictionary<Guid, ETSI.ARF.OpenAPI.WorldStorage.RelocalizationInformation>();
//m_computedPoses = new Dictionary<Guid, ETSI.ARF.OpenAPI.WorldAnalysis.Pose>();
m_subscriptionsPoses = new Dictionary<Guid, SubscriptionInfo>();
// sync
var httpClient = new BasicHTTPClient(waServer.URI);
apiClient = new WorldAnalysisClient(httpClient);
......@@ -47,6 +83,12 @@ public class WorldAnalysisREST : MonoBehaviour, WorldAnalysisInterface
/// </summary>
protected void Update()
{
ManageSubscriptionValidity();
// todo: Call subscription callback(s) here or in the websocket?!?
foreach (KeyValuePair<Guid, SubscriptionInfo> subPose in m_subscriptionsPoses)
{
}
}
#endregion
......@@ -62,29 +104,130 @@ public class WorldAnalysisREST : MonoBehaviour, WorldAnalysisInterface
Debug.Log("[REST] WA Version: " + ver);
}
public string GetWebSocketEndpoint()
public void PrintCapabilities(Capability[] capabilities)
{
string res = "";
Debug.Log("[REST] Got " + capabilities.Length + " capabilities.");
foreach (var item in capabilities)
{
res += "\nCapability: " + item.TrackableType + " Version: " + item.EncodingInformation.Version;
}
res += "\nEnd of capabilities.";
Debug.Log("[REST] Capabilities: " + res);
}
#endregion
#region Communication system
private void CreateWebHookServer()
{
throw new Exception("[REST] CreateWebHookServer(): Not implemented!");
}
private void DestroyWebHookServer()
{
return;
}
public WebSocket OpenWebSocketClient(string url)
{
string res = "empty";
webSocket = new WebSocketSharp.WebSocket(url);
//
// Define standard callbacks
//
webSocket.OnOpen += (sender, e) =>
{
Debug.Log("[WS] Connected");
websocketConnected = true;
webSocket.Send("RegisterClient:UnitySceneManagement");
};
webSocket.OnClose += (sender, e) =>
{
Debug.Log("[WS] Disconnected");
websocketConnected = false;
};
webSocket.OnError += (sender, e) => Debug.Log("[WS] Error!");
webSocket.OnMessage += (sender, e) => HandleWebSocketClient(e.Data);
webSocket.Connect();
SubscriptionSingleRequest param = new SubscriptionSingleRequest();
param.Mode = Mode_WorldAnalysis.DEVICE_TO_TRACKABLES;
param.Target = Guid.Parse("fa8bbe40-8052-11ec-a8a3-0242ac120002"); // test
return webSocket;
}
SubscriptionSingle response = apiClient.SubscribeToPose(token, "1", param);
res = response.WebsocketUrl;
return res;
}
public void PrintCapabilities()
private void OnDestroy()
{
string res = "Capabilities:";
// State: red
CloseWebSocketClient();
}
Response2 cap = apiClient.GetCapabilities(token, "1");
foreach (var item in cap.Capabilities)
private void CloseWebSocketClient()
{
if (websocketConnected)
{
res += "\n" + item.TrackableType;
webSocket.Send("UnregisterClient");
webSocket.Close();
}
}
bool ok = false;
public void HandleWebSocketClient(string data)
{
Debug.Log("[WS] Receiving: " + data);
if (data.Contains("You are now registered"))
{
ok = true;
if (isDebug) webSocket.Send("PoseStart:10"); // test
}
else if (data == "PoseStop")
{
//SetColor(Color.yellow);
}
else if (ok)
{
if (data.Contains("estimationState"))
{
// Handle pose
ETSI.ARF.OpenAPI.WorldAnalysis.Pose p = JsonUtility.FromJson<ETSI.ARF.OpenAPI.WorldAnalysis.Pose>(data);
Debug.Log("[WS][Pose] State: " + p.EstimationState.ToString());
PoseEstimationResult res = p.EstimationState == PoseEstimationState.OK ? PoseEstimationResult.OK : PoseEstimationResult.FAILURE;
// Search the corresponding callbacks
foreach (var item in m_subscriptionsPoses.Values)
{
if (p.Uuid == item.uuidTarget)
{
item.callback(res, p);
}
}
}
}
}
#endregion
#region Lifecycle
/// <summary>
/// Check the validity of all subscriptions and delete one if needed
/// </summary>
protected void ManageSubscriptionValidity()
{
if (m_subscriptionsPoses.Count == 0) return;
List<Guid> subscriptionToDelete = new List<Guid>();
foreach (KeyValuePair<Guid, SubscriptionInfo> sub in m_subscriptionsPoses)
{
float validity = sub.Value.timeValidity;
if (Time.time > validity)
{
subscriptionToDelete.Add(sub.Key);
}
}
foreach (Guid s in subscriptionToDelete)
{
Debug.Log("ETSI ARF : Subscription deleted " + s);
m_subscriptionsPoses.Remove(s);
}
Debug.Log("[REST] Capabilities: " + res);
}
#endregion
......@@ -94,57 +237,245 @@ public class WorldAnalysisREST : MonoBehaviour, WorldAnalysisInterface
//
public AskFrameRateResult SetPoseEstimationFramerate(string token, PoseConfigurationTrackableType type, EncodingInformationStructure encodingInformation, int minimumFramerate)
{
return AskFrameRateResult.NOT_SUPPORTED; ///We cannot set any framerate for tracking on ARKit and ARCore
PoseConfiguration poseConfig = new PoseConfiguration();
poseConfig.TrackableType = type;
poseConfig.EncodingInformation = encodingInformation;
poseConfig.Framerate = minimumFramerate;
apiClient.ConfigureFramerate(token, sessionID, poseConfig);
return AskFrameRateResult.OK;
}
public PoseEstimationResult GetLastPose(string token, Guid uuid, Mode_WorldAnalysis mode, out ETSI.ARF.OpenAPI.WorldAnalysis.Pose pose)
{
pose = null;
return PoseEstimationResult.OK;
pose = apiClient.GetPose(token, sessionID, uuid, mode);
return pose != null ? PoseEstimationResult.OK : PoseEstimationResult.NOT_SUPPORTED;
}
public PoseEstimationResult[] GetLastPoses(string token, Guid[] uuids, Mode_WorldAnalysis[] modes, out ETSI.ARF.OpenAPI.WorldAnalysis.Pose[] poses)
{
poses = null;
return null;
if (uuids.Length != modes.Length)
{
Debug.LogError("[REST] ETSI ARF: Get poses: uuids and modes array do no have the same length");
poses = null;
return null;
}
PoseEstimationResult[] resul = new PoseEstimationResult[uuids.Length];
poses = new ETSI.ARF.OpenAPI.WorldAnalysis.Pose[uuids.Length];
List<Anonymous> uuidList = new List<Anonymous>();
Response poses_ = apiClient.GetPoses(token, sessionID, uuidList.ToArray());
List<ETSI.ARF.OpenAPI.WorldAnalysis.Pose> posesList = poses_.Poses as List<ETSI.ARF.OpenAPI.WorldAnalysis.Pose>;
if (poses_ != null && posesList != null && posesList.Count > 0)
{
for (int i = 0; i < uuids.Length; i++)
{
PoseEstimationResult poseResul = new PoseEstimationResult();
resul[i] = poseResul;
poses[i] = posesList[i];
}
return resul;
}
else
{
poses = null;
return null;
}
}
public InformationSubscriptionResult SubscribeToPose(string token, Guid uuid, Mode_WorldAnalysis mode, PoseCallback callback, ref int validity, out Guid subscriptionUUID)
{
subscriptionUUID = Guid.Empty;
// Todo: Maintain the callback to the subscription id
// Get capabilities?
// Get reloc info?
SubscriptionSingleRequest body = new SubscriptionSingleRequest();
body.Target = uuid;
body.Mode = mode;
body.Validity = validity;
body.WebhookUrl = callback == null ? "" : "https:\\..."; // empty -> app will use websockets (client)!
// Get subscription info from the REST server
SubscriptionSingle response = apiClient.SubscribeToPose(token, sessionID, body);
subscriptionUUID = response.Uuid;
// We add the subscription
SubscriptionInfo sub = new SubscriptionInfo();
sub.uuid = response.Uuid;
sub.timeValidity = Time.time + (validity / 1000.0f);
sub.pose = new ETSI.ARF.OpenAPI.WorldAnalysis.Pose();
sub.pose.Mode = mode;
sub.uuidTarget = uuid;
sub.callback = callback;
m_subscriptionsPoses.Add(sub.uuid, sub);
if (!string.IsNullOrEmpty(response.WebhookUrl))
{
CloseWebSocketClient();
// todo: create a REST server so that the WA server can send pose update to it
// How to auto-generate the C# REST server for pose for Unity?
CreateWebHookServer();
}
else
{
DestroyWebHookServer();
// todo: Open the websocket?
string websocketUrl = response.WebsocketUrl;
if (isDebug) websocketUrl = "ws://localhost:61788/ws"; // for tests
if (string.IsNullOrEmpty(websocketUrl))
{
// Create the WebSockets client here (NOT in the scene scripts)
if (!websocketConnected) OpenWebSocketClient(websocketUrl);
}
else throw new Exception("[REST] No valid WebSockets URL in server reponse.");
}
return InformationSubscriptionResult.OK;
}
public InformationSubscriptionResult[] SubscribeToPoses(string token, Guid[] uuids, Mode_WorldAnalysis[] modes, PoseCallback callback, ref int validity, out Guid[] subscriptionUUIDs)
{
subscriptionUUIDs = null;
return null;
if (uuids.Length != 0 && uuids.Length == modes.Length)
{
InformationSubscriptionResult[] resul = new InformationSubscriptionResult[uuids.Length];
subscriptionUUIDs = new Guid[uuids.Length];
for (int i = 0; i < uuids.Length; i++)
{
resul[i] = SubscribeToPose(token, uuids[i], modes[i], callback, ref validity, out subscriptionUUIDs[i]);
}
return resul;
}
else
{
Debug.LogError("[REST] ETSI ARF: Subscribe poses: uuids and modes array do no have the same length");
subscriptionUUIDs = null;
return null;
}
}
public InformationSubscriptionResult GetSubsription(string token, Guid subscriptionUUID, out PoseCallback callback, out Guid target, out Mode_WorldAnalysis mode, out int validity)
{
// default
callback = null;
target = Guid.Empty;
mode = Mode_WorldAnalysis.TRACKABLES_TO_DEVICE;
validity = 0;
return InformationSubscriptionResult.OK;
if (m_subscriptionsPoses.ContainsKey(subscriptionUUID))
{
// Check the server subscription
SubscriptionSingle sub = apiClient.GetSubscription(token, sessionID, subscriptionUUID);
// Check local one
if (sub.Uuid == subscriptionUUID)
{
SubscriptionInfo subInfo = m_subscriptionsPoses[subscriptionUUID];
callback = subInfo.callback;
target = subInfo.uuidTarget;
mode = subInfo.pose.Mode;
float validitySeconds = subInfo.timeValidity - Time.time;
validity = (int)validitySeconds * 1000;// conversion in ms
// Compare both
if (target == sub.Target && mode == sub.Mode && validity == sub.Validity)
return InformationSubscriptionResult.OK;
else
return InformationSubscriptionResult.NOT_ALLOWED;
}
}
return InformationSubscriptionResult.UNKNOWN_ID;
}
public InformationSubscriptionResult UpdateSubscription(string token, Guid subscriptionUUID, Mode_WorldAnalysis mode, int validity, PoseCallback callback)
{
return InformationSubscriptionResult.OK;
// default
callback = null;
mode = Mode_WorldAnalysis.TRACKABLES_TO_DEVICE;
validity = 0;
if (m_subscriptionsPoses.ContainsKey(subscriptionUUID))
{
SubscriptionInfo sub = m_subscriptionsPoses[subscriptionUUID];
PoseCallback oldCB = sub.callback;
Body body = new Body();
body.Mode = mode;
body.Validity = validity;
body.WebhookUrl = callback == null ? "" : "https:\\..."; // empty -> app will use websockets (client)!
// Update subscription info in the REST server
SubscriptionSingle response = apiClient.UpdateSubscription(token, sessionID, subscriptionUUID, body);
// Update local data
sub.pose.Mode = response.Mode;
sub.callback = callback;
sub.timeValidity = Time.time + (validity / 1000.0f);
//
// Recreate server/connection to ws only if someone changed!
//
if (oldCB != null && callback == null && !string.IsNullOrEmpty(response.WebhookUrl))
{
CloseWebSocketClient();
// todo: create a REST server so that the WA server can send pose update to it
// How to auto-generate the C# REST server for pose for Unity?
CreateWebHookServer();
}
else if (oldCB == null && callback != null && string.IsNullOrEmpty(response.WebhookUrl))
{
DestroyWebHookServer();
// todo: Open the websocket?
string websocketUrl = response.WebsocketUrl;
if (isDebug) websocketUrl = "ws://localhost:61788/ws"; // for tests
if (string.IsNullOrEmpty(websocketUrl))
{
// Create the WebSockets client here (NOT in the scene scripts)
if (!websocketConnected) OpenWebSocketClient(websocketUrl);
}
else throw new Exception("[REST] No valid WebSockets URL in server reponse.");
}
return InformationSubscriptionResult.OK;
}
return InformationSubscriptionResult.UNKNOWN_ID;
}
public InformationSubscriptionResult UnSubscribeToPose(Guid subscriptionUUID)
{
return InformationSubscriptionResult.OK;
if (m_subscriptionsPoses.ContainsKey(subscriptionUUID))
{
apiClient.UnsubscribeFromPose(token, sessionID, subscriptionUUID);
m_subscriptionsPoses.Remove(subscriptionUUID);
if (m_subscriptionsPoses.Count == 0)
{
// Close the connection via websockets
CloseWebSocketClient();
}
return InformationSubscriptionResult.OK;
}
return InformationSubscriptionResult.UNKNOWN_ID;
}
public CapabilityResult GetCapabilities(string token, out Capability[] capabilities)
{
capabilities = null;
return CapabilityResult.OK;
Response2 cap = apiClient.GetCapabilities(token, sessionID);
if (cap == null || cap.Capabilities == null || cap.Capabilities.Count == 0)
{
capabilities = null;
return CapabilityResult.FAIL;
}
else
{
capabilities = new Capability[cap.Capabilities.Count];
cap.Capabilities.CopyTo(capabilities, 0);
return CapabilityResult.OK;
}
}
public CapabilityResult GetCapability(string token, Guid uuid, out bool isSupported, out TypeWorldStorage type, out Capability[] capability)
......@@ -152,7 +483,21 @@ public class WorldAnalysisREST : MonoBehaviour, WorldAnalysisInterface
isSupported = false;
type = TypeWorldStorage.UNKNOWN;
capability = null;
return CapabilityResult.OK;
Response3 cap = apiClient.GetSupport(token, sessionID, uuid);
if (cap == null || cap.Capabilities == null || cap.Capabilities.Count == 0)
{
isSupported = false;
capability = null;
return CapabilityResult.FAIL;
}
else
{
isSupported = true;
capability = new Capability[cap.Capabilities.Count];
cap.Capabilities.CopyTo(capability, 0);
return CapabilityResult.OK;
}
}
#endregion
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment