Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
233 changes: 224 additions & 9 deletions Assets/Scripts/EphysLink/CommunicationManager.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;
using System.IO;
using System.Linq;
using BestHTTP.SocketIO3;
using UnityEngine;
Expand Down Expand Up @@ -339,6 +340,15 @@ private void GetVersion(Action<string> onSuccessCallback, Action onErrorCallback
.Emit("get_version");
}

/// <summary>
/// Get Ephys Link version.
/// </summary>
/// <returns>Version number.</returns>
private async Awaitable<string> GetVersion()
{
return await EmitAndGetStringResponse<object>("get_version", null);
}

/// <summary>
/// Get the platform type.
/// </summary>
Expand All @@ -357,6 +367,15 @@ public void GetPlatformType(Action<string> onSuccessCallback, Action onErrorCall
.Emit("get_platform_type");
}

/// <summary>
/// Get the platform type.
/// </summary>
/// <returns>Platform type.</returns>
public async Awaitable<string> GetPlatformType()
{
return await EmitAndGetStringResponse<object>("get_platform_type", null);
}

/// <summary>
/// Get manipulators event sender.
/// </summary>
Expand Down Expand Up @@ -387,6 +406,18 @@ public void GetManipulators(
.Emit("get_manipulators");
}

/// <summary>
/// Get connected manipulators and some basic information about them.
/// </summary>
/// <returns>Manipulators and their information.</returns>
public async Awaitable<GetManipulatorsResponse> GetManipulators()
{
return await EmitAndGetResponse<GetManipulatorsResponse, object>(
"get_manipulators",
null
);
}

/// <summary>
/// Request the current position of a manipulator (mm).
/// </summary>
Expand Down Expand Up @@ -419,6 +450,19 @@ public void GetPosition(
.Emit("get_position", manipulatorId);
}

/// <summary>
/// Request the current position of a manipulator (mm).
/// </summary>
/// <param name="manipulatorId">ID of the manipulator to get teh position of.</param>
/// <returns><see cref="PositionalResponse" /> with manipulator's position.</returns>
public async Awaitable<PositionalResponse> GetPosition(string manipulatorId)
{
return await EmitAndGetResponse<PositionalResponse, string>(
"get_position",
manipulatorId
);
}

/// <summary>
/// Request the current angles of a manipulator.
/// </summary>
Expand Down Expand Up @@ -450,6 +494,16 @@ public void GetAngles(
.Emit("get_angles", manipulatorId);
}

/// <summary>
/// Request the current angles of a manipulator.
/// </summary>
/// <param name="manipulatorId">ID of the manipulator to get the position of</param>
/// <returns><see cref="AngularResponse" /> with manipulator's angles.</returns>
public async Awaitable<AngularResponse> GetAngles(string manipulatorId)
{
return await EmitAndGetResponse<AngularResponse, string>("get_angles", manipulatorId);
}

public void GetShankCount(
string manipulatorId,
Action<int> onSuccessCallback,
Expand All @@ -475,6 +529,19 @@ public void GetShankCount(
.Emit("get_shank_count", manipulatorId);
}

/// <summary>
/// Request the number of shanks on a manipulator.
/// </summary>
/// <param name="manipulatorId">ID of the manipulator to get the shank count of.</param>
/// <returns><see cref="ShankCountResponse" /> with the number of shanks.</returns>
public async Awaitable<ShankCountResponse> GetShankCount(string manipulatorId)
{
return await EmitAndGetResponse<ShankCountResponse, string>(
"get_shank_count",
manipulatorId
);
}

/// <summary>
/// Request a manipulator be moved to a specific position.
/// </summary>
Expand Down Expand Up @@ -507,6 +574,19 @@ public void SetPosition(
.Emit("set_position", ToJson(request));
}

/// <summary>
/// Request a manipulator be moved to a specific position.
/// </summary>
/// <param name="request">Goto position request object</param>
/// <returns><see cref="PositionalResponse" /> with the manipulator's new position.</returns>
public async Awaitable<PositionalResponse> SetPosition(SetPositionRequest request)
{
return await EmitAndGetResponse<PositionalResponse, SetPositionRequest>(
"set_position",
request
);
}

/// <summary>
/// Request a manipulator drive down to a specific depth.
/// </summary>
Expand Down Expand Up @@ -538,6 +618,19 @@ Action<string> onErrorCallback
.Emit("set_depth", ToJson(request));
}

/// <summary>
/// Request a manipulator drive down to a specific depth.
/// </summary>
/// <param name="request">Drive to depth request</param>
/// <returns><see cref="SetDepthResponse" /> with the manipulator's new depth.</returns>
public async Awaitable<SetDepthResponse> SetDepth(SetDepthRequest request)
{
return await EmitAndGetResponse<SetDepthResponse, SetDepthRequest>(
"set_depth",
request
);
}

/// <summary>
/// Set the inside brain state of a manipulator.
/// </summary>
Expand Down Expand Up @@ -570,7 +663,20 @@ public void SetInsideBrain(
}

/// <summary>
/// Request a manipulator stops moving.
/// Set the inside brain state of a manipulator.
/// </summary>
/// <param name="request">Set inside brain request.</param>
/// <returns><see cref="BooleanStateResponse" /> with the manipulator's new inside brain state.</returns>
public async Awaitable<BooleanStateResponse> SetInsideBrain(SetInsideBrainRequest request)
{
return await EmitAndGetResponse<BooleanStateResponse, SetInsideBrainRequest>(
"set_inside_brain",
request
);
}

/// <summary>
/// Request a manipulator stops moving.
/// </summary>
/// <param name="manipulatorId"></param>
/// <param name="onSuccessCallback"></param>
Expand All @@ -585,19 +691,25 @@ Action<string> onErrorCallback
.Socket.ExpectAcknowledgement<string>(data =>
{
if (DataKnownAndNotEmpty(data))
{
// Non-empty response means error.
onErrorCallback?.Invoke(data);
}
else
{
// Empty response means success.
onSuccessCallback?.Invoke();
}
})
.Emit("stop", manipulatorId);
}

/// <summary>
/// Request a manipulator stops moving.
/// </summary>
/// <param name="manipulatorId">ID of the manipulator to stop</param>
/// <returns>Empty string if successful, error message if failed.</returns>
public async Awaitable<string> Stop(string manipulatorId)
{
return await EmitAndGetStringResponse("stop", manipulatorId);
}

/// <summary>
/// Request all movement to stop.
/// </summary>
Expand All @@ -609,33 +721,136 @@ public void StopAll(Action onSuccessCallback, Action<string> onErrorCallback)
.Socket.ExpectAcknowledgement<string>(data =>
{
if (DataKnownAndNotEmpty(data))
{
// Non-empty response means error.
onErrorCallback?.Invoke(data);
}
else
{
// Empty response means success.
onSuccessCallback?.Invoke();
}
})
.Emit("stop_all");
}

/// <summary>
/// Request all manipulators to stop.
/// </summary>
/// <returns>Empty string if successful, error message if failed.</returns>
public async Awaitable<string> StopAll()
{
return await EmitAndGetStringResponse<object>("stop_all", null);
}

#endregion

#region Utility Functions

/// <summary>
/// Quick error handler to log the error string if it exists.
/// </summary>
/// <param name="error">Error response to check.</param>
/// <returns>True if there was an error, false otherwise.</returns>
public static bool HasError(string error)
{
// Shortcut exit if there was no error.
if (string.IsNullOrEmpty(error))
return false;

// Log the error.
Debug.LogError(error);

// Return true to indicate an error.
return true;
}

#endregion

#region Helper functions

/// <summary>
/// Generic function to emit and event and get a response from the server.
/// </summary>
/// <param name="eventName">Event to emit to.</param>
/// <param name="requestParameter">Parameter to send with the event.</param>
/// <typeparam name="T">Expected (parsed) response type.</typeparam>
/// <typeparam name="TR">Type of the request parameter.</typeparam>
/// <returns>Response from server. Parsed to <see cref="T" /> if it's not a string.</returns>
/// <exception cref="InvalidDataException">Invalid response from server (empty or unknown).</exception>
private async Awaitable<T> EmitAndGetResponse<T, TR>(string eventName, TR requestParameter)
{
// Query server and capture response.
var dataCompletionSource = new AwaitableCompletionSource<string>();
_connectionManager
.Socket.ExpectAcknowledgement<string>(data => dataCompletionSource.SetResult(data))
.Emit(
eventName,
typeof(TR) == typeof(string) ? requestParameter : ToJson(requestParameter)
);

// Wait for data.
var data = await dataCompletionSource.Awaitable;

// Return data if it exists. Parse if return type is not string.
if (DataKnownAndNotEmpty(data))
return ParseJson<T>(data);

// Throw exception if data is empty.
throw new InvalidDataException($"{eventName} invalid response: {data}");
}

/// <summary>
/// Emit an event and get a string response from the server.
/// </summary>
/// <param name="eventName">Event to emit to.</param>
/// <param name="requestParameter">Parameter to send with the event.</param>
/// <typeparam name="TR">Type of the request parameter.</typeparam>
/// <returns>Response from server as a string.</returns>
private async Awaitable<string> EmitAndGetStringResponse<TR>(
string eventName,
TR requestParameter
)
{
// Query server and capture response.
var dataCompletionSource = new AwaitableCompletionSource<string>();
_connectionManager
.Socket.ExpectAcknowledgement<string>(data => dataCompletionSource.SetResult(data))
.Emit(
eventName,
typeof(TR) == typeof(string) ? requestParameter : ToJson(requestParameter)
);

// Wait for data.
var data = await dataCompletionSource.Awaitable;

// Return data.
return data;
}

/// <summary>
/// Check if data is not empty and is not the "unkown event" error.
/// </summary>
/// <param name="data">Data to check.</param>
/// <returns>True if data is not empty and not the "unkown event" error, false otherwise.</returns>
private static bool DataKnownAndNotEmpty(string data)
{
return !string.IsNullOrEmpty(data) && !data.Equals(UNKOWN_EVENT);
}

/// <summary>
/// Parse a JSON string into a data object.
/// </summary>
/// <param name="json">JSON string to parse.</param>
/// <typeparam name="T">Type of the data object.</typeparam>
/// <returns>Parsed data object.</returns>
private static T ParseJson<T>(string json)
{
return JsonUtility.FromJson<T>(json);
}

/// <summary>
/// Convert a data object into a JSON string.
/// </summary>
/// <param name="data">Data object to convert.</param>
/// <typeparam name="T">Type of the data object.</typeparam>
/// <returns>JSON string.</returns>
private static string ToJson<T>(T data)
{
return JsonUtility.ToJson(data);
Expand Down
Loading