Skip to content
GitLab
Explore
Sign in
Primary navigation
Search or go to…
Project
U
Unity World Analysis Package
Manage
Activity
Members
Labels
Plan
Issues
Issue boards
Milestones
Iterations
Wiki
Requirements
Code
Merge requests
Repository
Branches
Commits
Tags
Repository graph
Compare revisions
Snippets
Locked files
Build
Pipelines
Jobs
Pipeline schedules
Test cases
Artifacts
Deploy
Releases
Package Registry
Container Registry
Model registry
Operate
Environments
Terraform modules
Monitor
Incidents
Service Desk
Analyze
Value stream analytics
Contributor analytics
CI/CD analytics
Repository analytics
Code review analytics
Issue analytics
Insights
Model experiments
Help
Help
Support
GitLab documentation
Compare GitLab plans
Community forum
Contribute to GitLab
Provide feedback
Keyboard shortcuts
?
Snippets
Groups
Projects
Show more breadcrumbs
ARF
World Analysis API helpers
Unity World Analysis Package
Commits
24e858d2
Commit
24e858d2
authored
8 months ago
by
Sylvain Renault
Browse files
Options
Downloads
Patches
Plain Diff
API draft implementation and integration of a websockets client.
parent
cafd4e6c
No related branches found
No related tags found
1 merge request
!2
Finalisation of REST and websocket client functions and subscription methodics.
Changes
1
Hide whitespace changes
Inline
Side-by-side
Showing
1 changed file
Runtime/Scripts/WorldAnalysisREST.cs
+381
-36
381 additions, 36 deletions
Runtime/Scripts/WorldAnalysisREST.cs
with
381 additions
and
36 deletions
Runtime/Scripts/WorldAnalysisREST.cs
+
381
−
36
View file @
24e858d2
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
p
rivate
WorldAnalysisClient
apiClient
;
[
Space
(
8
)]
p
ublic
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
...
...
This diff is collapsed.
Click to expand it.
Preview
0%
Loading
Try again
or
attach a new file
.
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Save comment
Cancel
Please
register
or
sign in
to comment