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
using WebSocketSharp;
lacoche
committed
//Implementation of the WorldAnalysis interface
public class WorldAnalysisREST : MonoBehaviour, WorldAnalysisInterface
{
//
// 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;
//
// 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;
}
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
}
#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
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)
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();
return webSocket;
}
private void OnDestroy()
// State: red
CloseWebSocketClient();
}
private void CloseWebSocketClient()
{
if (websocketConnected)
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
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);
}
}
#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
{
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
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;
}
lacoche
committed
}
public InformationSubscriptionResult SubscribeToPose(string token, Guid uuid, Mode_WorldAnalysis mode, PoseCallback callback, ref int validity, out Guid subscriptionUUID)
lacoche
committed
{
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
// 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.");
}
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
{
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
// 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;
lacoche
committed
}
public InformationSubscriptionResult UnSubscribeToPose(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
CloseWebSocketClient();
}
return InformationSubscriptionResult.OK;
}
return InformationSubscriptionResult.UNKNOWN_ID;
lacoche
committed
}
public CapabilityResult GetCapabilities(string token, out Capability[] capabilities)
{
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;
}
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;
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;
}