Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for ROS actions #108

Merged
merged 23 commits into from
Aug 15, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
f7b6deb
initial action idl generation
hoffmann-stefan Jun 21, 2022
fdba3a6
extend idl generation to include interfaces for "action wrapper types"
hoffmann-stefan Jun 21, 2022
0c2b20a
add accessors to `UUID` and `Time` properties for "action wrapper types"
hoffmann-stefan Jun 22, 2022
550e70c
add ActionDefinitionStaticMemberCache
hoffmann-stefan Jun 22, 2022
ee82b19
add public action client API without implementation
hoffmann-stefan Jun 22, 2022
1ebcde8
rename `ServiceIsReady()` to `ServerIsReady() `and move to base class
hoffmann-stefan Jun 21, 2022
2fa3d42
add helper for converting guid <-> uuid
hoffmann-stefan Jun 23, 2022
c0cf14d
implement basic action client
hoffmann-stefan Jun 24, 2022
8bd8edd
add public action server API without implementation
hoffmann-stefan Jun 29, 2022
c7da1fc
add missing license header
hoffmann-stefan Jun 29, 2022
b356a25
change IsCancelRequested to IsCanceling, add IsExecuting
hoffmann-stefan Jul 19, 2022
28790e8
add ReadFromMessageHandle/WriteToMessageHandle helper mehtods
hoffmann-stefan Jul 19, 2022
8ed0256
implement basic action server
hoffmann-stefan Jul 19, 2022
b4503f9
add example action client and server
hoffmann-stefan Jul 19, 2022
9522815
make local variable name more obvious
hoffmann-stefan Jul 22, 2022
41ed592
use return value from Interlocked.CompareExchange
hoffmann-stefan Jul 22, 2022
23f5e46
reorder HandleGoalResponse, HandleStatusMessage and HandleFeedbackMes…
hoffmann-stefan Jul 28, 2022
12da3ee
handle action specific take failed error codes
hoffmann-stefan Jul 28, 2022
5a504ff
publish status before sending the result response
hoffmann-stefan Jul 28, 2022
0416620
fix typo in dependency
hoffmann-stefan Aug 17, 2022
3187f5a
fix bool pInvokes in actions
hoffmann-stefan May 19, 2023
ac13d48
add unittests for actions
hoffmann-stefan May 29, 2023
fe7a781
update README.md
hoffmann-stefan Aug 15, 2023
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
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ The current set of features include:
- Generation of all builtin ROS types
- Support for publishers and subscriptions
- Support for clients and services
- Support action clients and servers
- Cross-platform support (Linux, Windows, Windows IoT Core, UWP)

What's missing?
Expand All @@ -30,7 +31,6 @@ Lots of things!
- Unicode types
- String constants (specifically BoundedString)
- Component nodes
- Actions
- Tests
- Documentation
- More examples (e.g. IoT, VB, UWP, HoloLens, etc.)
Expand Down
457 changes: 457 additions & 0 deletions rcldotnet/ActionClient.cs

Large diffs are not rendered by default.

100 changes: 100 additions & 0 deletions rcldotnet/ActionClientGoalHandle.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
/* Copyright 2022 Stefan Hoffmann <[email protected]>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

using System;
using System.Threading;
using System.Threading.Tasks;
using builtin_interfaces.msg;

namespace ROS2
{
public abstract class ActionClientGoalHandle
{
// Only allow internal subclasses.
internal ActionClientGoalHandle()
{
}

public abstract Guid GoalId { get; }

public abstract bool Accepted { get; }

public abstract Time Stamp { get; }

public abstract ActionGoalStatus Status { get; internal set; }
}

public sealed class ActionClientGoalHandle<TAction, TGoal, TResult, TFeedback> : ActionClientGoalHandle
where TAction : IRosActionDefinition<TGoal, TResult, TFeedback>
where TGoal : IRosMessage, new()
where TResult : IRosMessage, new()
where TFeedback : IRosMessage, new()
{
private readonly ActionClient<TAction, TGoal, TResult, TFeedback> _actionClient;

private TaskCompletionSource<TResult> _resultTaskCompletionSource;

// No public constructor.
internal ActionClientGoalHandle(
ActionClient<TAction, TGoal, TResult, TFeedback> actionClient,
Guid goalId,
bool accepted,
Time stamp,
Action<TFeedback> feedbackCallback)
{
GoalId = goalId;
Accepted = accepted;
Stamp = stamp;
FeedbackCallback = feedbackCallback;
_actionClient = actionClient;
}

public override Guid GoalId { get; }

public override bool Accepted { get; }

public override Time Stamp { get; }

public override ActionGoalStatus Status { get; internal set; }

internal Action<TFeedback> FeedbackCallback { get; }

// TODO: (sh) should we return the CancelGoal_Response? Wrap it in type with dotnet enum/Guid?
public Task CancelGoalAsync()
{
return _actionClient.CancelGoalAsync(this);
}

public Task<TResult> GetResultAsync()
{
// Fast case to avoid allocation and calling Interlocked.CompareExchange.
if (_resultTaskCompletionSource != null)
{
return _resultTaskCompletionSource.Task;
}

var resultTaskCompletionSource = new TaskCompletionSource<TResult>();

var oldResultTcs = Interlocked.CompareExchange(ref _resultTaskCompletionSource, resultTaskCompletionSource, null);
if (oldResultTcs != null)
{
// Some other thread was first.
return oldResultTcs.Task;
}

return _actionClient.GetResultAsync(this, resultTaskCompletionSource);
}
}
}
259 changes: 259 additions & 0 deletions rcldotnet/ActionDefinitionStaticMemberCache.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,259 @@
/* Copyright 2022 Stefan Hoffmann <[email protected]>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

using System;
using System.Reflection;
using System.Runtime.InteropServices;

namespace ROS2
{
internal static class ActionDefinitionStaticMemberCache<TAction, TGoal, TResult, TFeedback>
where TAction : IRosActionDefinition<TGoal, TResult, TFeedback>
where TGoal : IRosMessage, new()
where TResult : IRosMessage, new()
where TFeedback : IRosMessage, new()
{
private static readonly IntPtr s_typeSupport;
private static readonly Func<IRosActionSendGoalRequest<TGoal>> s_createSendGoalRequest;
private static readonly Func<SafeHandle> s_createSendGoalRequestHandle;
private static readonly Func<IRosActionSendGoalResponse> s_createSendGoalResponse;
private static readonly Func<SafeHandle> s_createSendGoalResponseHandle;
private static readonly Func<IRosActionGetResultRequest> s_createGetResultRequest;
private static readonly Func<SafeHandle> s_createGetResultRequestHandle;
private static readonly Func<IRosActionGetResultResponse<TResult>> s_createGetResultResponse;
private static readonly Func<SafeHandle> s_createGetResultResponseHandle;
private static readonly Func<IRosActionFeedbackMessage<TFeedback>> s_createFeedbackMessage;
private static readonly Func<SafeHandle> s_createFeedbackMessageHandle;

static ActionDefinitionStaticMemberCache()
{
TypeInfo typeInfo = typeof(TAction).GetTypeInfo();

MethodInfo getTypeSupport = typeInfo.GetDeclaredMethod("__GetTypeSupport");
if (getTypeSupport != null)
{
try
{
s_typeSupport = (IntPtr)getTypeSupport.Invoke(null, new object[] { });
}
catch
{
s_typeSupport = IntPtr.Zero;
}
}
else
{
s_typeSupport = IntPtr.Zero;
}

s_createSendGoalRequest = CreateDelegateForDeclaredMethod<Func<IRosActionSendGoalRequest<TGoal>>>(
typeInfo,
"__CreateSendGoalRequest");

s_createSendGoalRequestHandle = CreateDelegateForDeclaredMethod<Func<SafeHandle>>(
typeInfo,
"__CreateSendGoalRequestHandle");

s_createSendGoalResponse = CreateDelegateForDeclaredMethod<Func<IRosActionSendGoalResponse>>(
typeInfo,
"__CreateSendGoalResponse");

s_createSendGoalResponseHandle = CreateDelegateForDeclaredMethod<Func<SafeHandle>>(
typeInfo,
"__CreateSendGoalResponseHandle");

s_createGetResultRequest = CreateDelegateForDeclaredMethod<Func<IRosActionGetResultRequest>>(
typeInfo,
"__CreateGetResultRequest");

s_createGetResultRequestHandle = CreateDelegateForDeclaredMethod<Func<SafeHandle>>(
typeInfo,
"__CreateGetResultRequestHandle");

s_createGetResultResponse = CreateDelegateForDeclaredMethod<Func<IRosActionGetResultResponse<TResult>>>(
typeInfo,
"__CreateGetResultResponse");

s_createGetResultResponseHandle = CreateDelegateForDeclaredMethod<Func<SafeHandle>>(
typeInfo,
"__CreateGetResultResponseHandle");

s_createFeedbackMessage = CreateDelegateForDeclaredMethod<Func<IRosActionFeedbackMessage<TFeedback>>>(
typeInfo,
"__CreateFeedbackMessage");

s_createFeedbackMessageHandle = CreateDelegateForDeclaredMethod<Func<SafeHandle>>(
typeInfo,
"__CreateFeedbackMessageHandle");
}

public static IntPtr GetTypeSupport()
{
// This is a Method because it could throw.
if (s_typeSupport == IntPtr.Zero)
{
throw CreateMethodNotDefinedCorrectlyException("__GetTypeSupport");
}

return s_typeSupport;
}

public static IRosActionSendGoalRequest<TGoal> CreateSendGoalRequest()
{
if (s_createSendGoalRequest != null)
{
return s_createSendGoalRequest();
}
else
{
throw CreateMethodNotDefinedCorrectlyException("__CreateSendGoalRequest");
}
}

public static SafeHandle CreateSendGoalRequestHandle()
{
if (s_createSendGoalRequestHandle != null)
{
return s_createSendGoalRequestHandle();
}
else
{
throw CreateMethodNotDefinedCorrectlyException("__CreateSendGoalRequestHandle");
}
}

public static IRosActionSendGoalResponse CreateSendGoalResponse()
{
if (s_createSendGoalResponse != null)
{
return s_createSendGoalResponse();
}
else
{
throw CreateMethodNotDefinedCorrectlyException("__CreateSendGoalResponse");
}
}

public static SafeHandle CreateSendGoalResponseHandle()
{
if (s_createSendGoalResponseHandle != null)
{
return s_createSendGoalResponseHandle();
}
else
{
throw CreateMethodNotDefinedCorrectlyException("__CreateSendGoalResponseHandle");
}
}

public static IRosActionGetResultRequest CreateGetResultRequest()
{
if (s_createGetResultRequest != null)
{
return s_createGetResultRequest();
}
else
{
throw CreateMethodNotDefinedCorrectlyException("__CreateGetResultRequest");
}
}

public static SafeHandle CreateGetResultRequestHandle()
{
if (s_createGetResultRequestHandle != null)
{
return s_createGetResultRequestHandle();
}
else
{
throw CreateMethodNotDefinedCorrectlyException("__CreateGetResultRequestHandle");
}
}

public static IRosActionGetResultResponse<TResult> CreateGetResultResponse()
{
if (s_createGetResultResponse != null)
{
return s_createGetResultResponse();
}
else
{
throw CreateMethodNotDefinedCorrectlyException("__CreateGetResultResponse");
}
}

public static SafeHandle CreateGetResultResponseHandle()
{
if (s_createGetResultResponseHandle != null)
{
return s_createGetResultResponseHandle();
}
else
{
throw CreateMethodNotDefinedCorrectlyException("__CreateGetResultResponseHandle");
}
}

public static IRosActionFeedbackMessage<TFeedback> CreateFeedbackMessage()
{
if (s_createFeedbackMessage != null)
{
return s_createFeedbackMessage();
}
else
{
throw CreateMethodNotDefinedCorrectlyException("__CreateFeedbackMessage");
}
}

public static SafeHandle CreateFeedbackMessageHandle()
{
if (s_createFeedbackMessageHandle != null)
{
return s_createFeedbackMessageHandle();
}
else
{
throw CreateMethodNotDefinedCorrectlyException("__CreateFeedbackMessageHandle");
}
}

private static TDelegate CreateDelegateForDeclaredMethod<TDelegate>(TypeInfo typeInfo, string methodName)
where TDelegate : Delegate
{
MethodInfo methodInfo = typeInfo.GetDeclaredMethod(methodName);
if (methodInfo != null)
{
try
{
return (TDelegate)methodInfo.CreateDelegate(typeof(TDelegate));
}
catch
{
return null;
}
}
else
{
return null;
}
}

private static Exception CreateMethodNotDefinedCorrectlyException(string methodName)
{
return new InvalidOperationException($"Type '{typeof(TAction).FullName}' did not define a correct {methodName} method.");
}
}
}
Loading