Newer
Older
using System.Collections;
using System.Collections.Generic;
lacoche
committed
using UnityEngine;
using ETSI.ARF.OpenAPI.WorldAnalysis;
using ETSI.ARF.WorldAnalysis;
lacoche
committed
using static WorldAnalysisInterface;
using ETSI.ARF.WorldAnalysis.REST;
lacoche
committed
lacoche
committed
//Implementation of the WorldAnalysis interface
public partial class WorldAnalysisREST : MonoBehaviour, WorldAnalysisInterface
lacoche
committed
{
[Serializable]
public class StringEvent : UnityEvent<string> { }
//
// Inspector variables
//
/// <summary>
/// WorldSAnalysisServer
/// </summary>
public WorldAnalysisServer waServer;
public string token = "ETSI-ARF-STF";
public string sessionID = "RESTful-API";
[Space(8)]
public bool isDebug = false;
//[Serializable]
//public class StringEvent : UnityEvent<string> { }
//public StringEvent webSocketMessage;
//
// Private members
//
private WorldAnalysisClient apiClient; // For sync calls
private WorldAnalysisClient apiClientAsync; // For async calls
//
// 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;
}
lacoche
committed
#region Unity_Methods
/// <summary>
/// Unity Awake Method
/// </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);
// async
//var httpClientAsync = new UnityWebRequestHttpClient(waServer.URI);
//apiClientAsync = new WorldAnalysisClient(httpClientAsync);
lacoche
committed
}
/// <summary>
/// Unity Start Method
/// </summary>
protected void Start()
{
}
/// <summary>
/// Unity Update Method
/// </summary>
protected void Update()
{
ManageSubscriptionValidity();
// todo: Call subscription callback(s) here or in the websocket?!?
foreach (KeyValuePair<Guid, SubscriptionInfo> subPose in m_subscriptionsPoses)
{
}
lacoche
committed
}
private void OnDestroy()
{
WebSocketClient_Close();
}
lacoche
committed
#endregion
#region Test methods
public void CheckServer()
{
string ping = AdminRequest.PingSync(waServer);
string state = AdminRequest.AdminSync(waServer);
string ver = AdminRequest.VersionSync(waServer);
Debug.Log("[REST] WA Ping: " + ping);
Debug.Log("[REST] WA State: " + state);
Debug.Log("[REST] WA Version: " + ver);
}
lacoche
committed
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
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 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);
}
}
#endregion
lacoche
committed
#region ARF_API
//
// Implementation of the endpoints
//
public AskFrameRateResult SetPoseEstimationFramerate(string token, PoseConfigurationTrackableType type, EncodingInformationStructure encodingInformation, int minimumFramerate)
lacoche
committed
{
PoseConfiguration poseConfig = new PoseConfiguration();
poseConfig.TrackableType = type;
poseConfig.EncodingInformation = encodingInformation;
poseConfig.Framerate = minimumFramerate;
apiClient.ConfigureFramerate(token, sessionID, poseConfig);
return AskFrameRateResult.OK;
lacoche
committed
}
public PoseEstimationResult GetLastPose(string token, Guid uuid, Mode_WorldAnalysis mode, out ETSI.ARF.OpenAPI.WorldAnalysis.Pose pose)
lacoche
committed
{
pose = apiClient.GetPose(token, sessionID, uuid, mode);
return pose != null ? PoseEstimationResult.OK : PoseEstimationResult.NOT_SUPPORTED;
lacoche
committed
}
public PoseEstimationResult[] GetLastPoses(string token, Guid[] uuids, Mode_WorldAnalysis[] modes, out ETSI.ARF.OpenAPI.WorldAnalysis.Pose[] poses)
lacoche
committed
{
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<UuidAndMode> uuidList = new List<UuidAndMode>();
Poses poses_ = apiClient.GetPoses(token, sessionID, uuidList.ToArray());
List<ETSI.ARF.OpenAPI.WorldAnalysis.Pose> posesList = poses_.Poses1 as List<ETSI.ARF.OpenAPI.WorldAnalysis.Pose>;
if (poses_ != null && posesList != null && posesList.Count > 0)
{
for (int i = 0; i < uuids.Length; i++)
{
PoseEstimationResult poseResult = new PoseEstimationResult();
resul[i] = poseResult;
poses[i] = posesList[i];
}
return resul;
}
else
{
poses = null;
return null;
}
lacoche
committed
}
public InformationSubscriptionResult SubscribeToPose(string token, Guid uuid, Mode_WorldAnalysis mode, PoseCallback callback, ref int validity, out Guid subscriptionUUID)
lacoche
committed
{
// Todo: Maintain the callback to the subscription id
// Get capabilities?
// Get reloc info?
subscriptionUUID = Guid.Empty; // default
SubscriptionSingleRequest body = new SubscriptionSingleRequest();
body.Target = uuid;
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;
validity = response.Validity;
//SubscriptionMultipleRequest body_m = new SubscriptionMultipleRequest();
//body_m.Targets = new List<object>();
//body_m.Mode = new List<Mode_WorldAnalysis>();
//body_m.Validity = validity;
//body_m.WebhookUrl = callback == null ? "" : "https:\\..."; // empty -> app will use websockets (client)!
//SubscriptionMultiple response = apiClient.SubscribeToPose(token, sessionID, body_m);
// We add the subscription
SubscriptionInfo sub = new SubscriptionInfo();
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))
{
// Close other communication channel
WebSocketClient_Close();
// Create a REST server so that the WA server can send pose update to it
// How to auto-generate the C# REST webhook server for pose for Unity?
string webhookUrl = response.WebhookUrl;
WebHookServer_Create(webhookUrl);
}
else
{
// Close other communication channel
WebHookServer_Close();
// Open the websocket
string websocketUrl = "ws://" + response.WebsocketUrl + "/ws";
//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) WebSocketClient_Create(websocketUrl);
}
else throw new Exception("[REST] No valid WebSockets URL in server reponse.");
}
lacoche
committed
return InformationSubscriptionResult.OK;
}
public InformationSubscriptionResult[] SubscribeToPoses(string token, Guid[] uuids, Mode_WorldAnalysis[] modes, PoseCallback callback, ref int validity, out Guid[] subscriptionUUIDs)
lacoche
committed
{
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;
}
lacoche
committed
}
public InformationSubscriptionResult GetSubsription(string token, Guid subscriptionUUID, out PoseCallback callback, out Guid target, out Mode_WorldAnalysis mode, out int validity)
lacoche
committed
{
// default
lacoche
committed
callback = null;
lacoche
committed
mode = Mode_WorldAnalysis.TRACKABLES_TO_DEVICE;
validity = 0;
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;
lacoche
committed
}
public InformationSubscriptionResult UpdateSubscription(string token, Guid subscriptionUUID, Mode_WorldAnalysis mode, int validity, PoseCallback callback)
lacoche
committed
{
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
// 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))
{
// 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?
string webhookUrl = response.WebhookUrl;
WebHookServer_Create(webhookUrl);
}
else if (oldCB == null && callback != null && string.IsNullOrEmpty(response.WebhookUrl))
{
// 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) WebSocketClient_Create(websocketUrl);
}
else throw new Exception("[REST] No valid WebSockets URL in server reponse.");
}
return InformationSubscriptionResult.OK;
}
return InformationSubscriptionResult.UNKNOWN_ID;
lacoche
committed
}
public InformationSubscriptionResult UnsubscribeFromPose(Guid subscriptionUUID)
lacoche
committed
{
if (m_subscriptionsPoses.ContainsKey(subscriptionUUID))
{
apiClient.UnsubscribeFromPose(token, sessionID, subscriptionUUID);
m_subscriptionsPoses.Remove(subscriptionUUID);
if (m_subscriptionsPoses.Count == 0)
{
// Close the connection via websockets
}
return InformationSubscriptionResult.OK;
}
return InformationSubscriptionResult.UNKNOWN_ID;
lacoche
committed
}
public CapabilityResult GetCapabilities(string token, out Capability[] capabilities)
{
Capabilities cap = apiClient.GetCapabilities(token, sessionID);
if (cap == null || cap.Capabilities1 == null || cap.Capabilities1.Count == 0)
{
capabilities = null;
return CapabilityResult.FAIL;
}
else
{
capabilities = new Capability[cap.Capabilities1.Count];
cap.Capabilities1.CopyTo(capabilities, 0);
return CapabilityResult.OK;
}
lacoche
committed
}
public CapabilityResult GetCapability(string token, Guid uuid, out bool isSupported, out TypeWorldStorage type, out Capability[] capability)
lacoche
committed
{
isSupported = false;
type = TypeWorldStorage.UNKNOWN;
capability = null;
Supports 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;
}