{"id":30250489,"url":"https://github.com/noelex/rclnet","last_synced_at":"2025-12-24T03:58:34.000Z","repository":{"id":65902090,"uuid":"582499493","full_name":"noelex/rclnet","owner":"noelex","description":"Modern ROS 2 client library for .NET.","archived":false,"fork":false,"pushed_at":"2025-05-13T04:11:03.000Z","size":1271,"stargazers_count":31,"open_issues_count":4,"forks_count":4,"subscribers_count":7,"default_branch":"main","last_synced_at":"2025-08-01T01:43:10.581Z","etag":null,"topics":["csharp","dotnet","rcl","ros","ros2"],"latest_commit_sha":null,"homepage":"","language":"C#","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/noelex.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2022-12-27T03:04:03.000Z","updated_at":"2025-06-24T11:15:55.000Z","dependencies_parsed_at":"2025-02-26T16:36:32.338Z","dependency_job_id":null,"html_url":"https://github.com/noelex/rclnet","commit_stats":{"total_commits":167,"total_committers":1,"mean_commits":167.0,"dds":0.0,"last_synced_commit":"b92510d6309d930f90a00ff009a30f905c59391e"},"previous_names":[],"tags_count":15,"template":false,"template_full_name":null,"purl":"pkg:github/noelex/rclnet","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/noelex%2Frclnet","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/noelex%2Frclnet/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/noelex%2Frclnet/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/noelex%2Frclnet/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/noelex","download_url":"https://codeload.github.com/noelex/rclnet/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/noelex%2Frclnet/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":270552728,"owners_count":24605575,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-08-15T02:00:12.559Z","response_time":110,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["csharp","dotnet","rcl","ros","ros2"],"created_at":"2025-08-15T10:01:02.931Z","updated_at":"2025-10-25T00:03:42.472Z","avatar_url":"https://github.com/noelex.png","language":"C#","funding_links":[],"categories":[],"sub_categories":[],"readme":"# rclnet\nrclnet is a fast and easy-to-use .NET wrapper over ROS 2 client library, allowing .NET applications to interact with other ROS applications.\n\n## What's New in 2.0\n - Added support for .NET 10 and changed minimum supported .NET version to 8.0\n - ROS 2 Kilted Support\n - ROS 2 Jazzy Support by @AlrayQiu ([#39](https://github.com/noelex/rclnet/pull/39))\n - Simplified message generation workflow by @ha-ves ([#38](https://github.com/noelex/rclnet/pull/38))\n - String pooling is now disabled by default\n\n## Features\n- Completely asynchronous and `async`/`await` friendly.\n- Flexible asynchronous scheduling control to fit rclnet into existing applications.\n- Unified message generation for POCOs and blittable structures.\n- Intuitive ROS graph querying and monitoring APIs.\n- Easy-to-use POCO-based APIs.\n- Fast and zero managed heap allocation APIs operating directly on native message buffers.\n- Single package with runtime support for different ROS 2 distros.\n- Builtin support for querying topic messages and ROS graph events with [Reactive Extensions](https://github.com/dotnet/reactive).\n\n### Supported ROS Features\n| Feature                 | Status | Additional Information                                                                                                                                                                                                                       |\n| ----------------------- | ------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| Topics                  | ✅      | N/A                                                                                                                                                                                                                                          |\n| Services                | ✅      | N/A                                                                                                                                                                                                                                          |\n| Actions                 | ✅      | Managed implementation.                                                                                                                                                                                                                      |\n| Clocks                  | ✅      | Supports external time source by setting `use_sim_time` to `true`.\u003cbr/\u003e`CancellationTokenSource`s can also be configured to cancel with timeout measured by external clock.                                                                  |\n| Timers                  | ✅      | N/A                                                                                                                                                                                                                                          |\n| Guard Conditions        | ✅      | N/A                                                                                                                                                                                                                                          |\n| Events                  | ✅      | Event handlers can be registered via `SubscriptionOptions` or `PublisherOptions` when creating the subscirption or publisher.                                                                                                                |\n| ROS Graph               | ✅      | Managed implementation.                                                                                                                                                                                                                      |\n| Logging                 | ✅      | Supports logging to stdout, /rosout and log files. Configurable with `--ros-args`.                                                                                                                                                           |\n| [Content Filtered Topics](https://github.com/ros2/design/blob/918c09758ed4c0854aa128b9c8ed0051c21a6590/articles/content_filtering.md) | ✅      | Available since humble. \n| [Network Flow Endpoints](https://design.ros2.org/articles/unique_network_flows.html)  | ✅      | Available since galactic.\u003cbr/\u003eNetwork flow endpoints of publishers and subscriptions can be retrieved via `IRclPublisher.Endpoints` and `IRclSubscription.Endpoints` property.\u003cbr/\u003eUnique network flow endpoints requirement can be configured when creating `SubscriptionOptions` and `PublisherOptions`.|\n| [Service Introspection](https://github.com/ros-infrastructure/rep/blob/jacob/service_introspection/rep-2012.rst)   | ✅      | Available since iron. |\n| [Parameter Service](https://design.ros2.org/articles/ros_parameters.html)       | ⚠️      | Supports loading parameters from command-line arguments and parameter files.\u003cbr/\u003eLocally declared parameters are exposed via Parameter API.\u003cbr/\u003eParameter client is not implemented. |\n| [Lifecycle Nodes](https://design.ros2.org/articles/node_lifecycle.html)         | ❌      | N/A                                                                                                                                                                                                                                          |\n\n✅Supported ⚠️Partial support ❌Not supported ⏳In development\n\n## Supported Platforms\nSupported .NET Versions:\n- .NET 8\n- .NET 9\n- .NET 10\n\nSupported ROS 2 Distributions:\n- Foxy Fitzroy\n- Humble Hawksbill\n- Iron Irwini\n- Jazzy Jalisco\n- Kilted Kaiju\n\nSupported Operating Systems:\n- Ubuntu\n- Windows\n\nShould also work on macOS but untested.\n\n## Installing\nStable releases of rclnet are hosted on NuGet. You can install them using the following command:\n```\ndotnet add package Rcl.NET\n```\n\n## Generating Messages\n\n### Preparation\nrclnet does not ship with message definitions. In order to communicate with other ROS 2 nodes,\nyou need to generate messages first.\n\nMessage definitions are .NET classes / structs, you can either include messages in a console app\nwhich runs as an ROS 2 node, or compile separately in another library.\n\nProjects containing messages will have to meet the following requirements:\n- `Rcl.NET` (or `Rosidl.Runtime` if you are not using automated codegen) NuGet package is installed.\n- `AllowUnsafeBlocks` is set to `true`. This can be done by adding the following lines to the `.csproj` file:\n    ```xml\n    \u003cPropertyGroup\u003e\n        \u003cAllowUnsafeBlocks\u003etrue\u003c/AllowUnsafeBlocks\u003e\n    \u003c/PropertyGroup\u003e\n    ```\n- Runtime marshalling for the assembly is disabled. You can add the following line to somewhere in the source code of the project:\n    ```csharp\n    [assembly: System.Runtime.CompilerServices.DisableRuntimeMarshalling]\n    ```\n\nTo generate messages, you also need to add a `ros2cs.spec` file to somewhere in the project (usually the project root).\nA `ros2cs.spec` file contains configurations such as output directory and where to find packages,\nsee [here](https://github.com/noelex/rclnet/blob/main/src/ros2cs/ros2cs.spec) for detailed explanations.\n\n### Generating messages using automated codegen\nNow simply build the project and message definitions should appear in a directory named `Ros2csGeneratedInterfaces`.\n\nThe path to the spec file and output directory can also be customized using `Ros2csSpecFile` and `Ros2csOutputDir` MSBuild property, e.g.:\n```xml\n\u003cPropertyGroup\u003e\n  \u003cRos2csSpecFile\u003epath/to/spec/file\u003c/Ros2csSpecFile\u003e\n  \u003cRos2csOutputDir\u003eMyInterfaces\u003c/Ros2csOutputDir\u003e\n\u003c/PropertyGroup\u003e\n```\n\nPlease note that when using automated codegen, there's no need to specify `output` directive in the spec file as it's automatically determined during build.\n\n### Generating messages using `ros2cs` tool\nFirst install `ros2cs` tool using NuGet package manager:\n```\ndotnet tool install -g ros2cs\n```\n\nNow you should be able to generate messages for the spec file:\n```\nros2cs /path/to/ros2cs.spec\n```\n\n`ros2cs` tool also supports overriding directives defined in the spec file. You can run `ros2cs --help` for more details.\n\n## API Usage Showcase\n### Subscribing\n```csharp\nawait using var ctx = new RclContext(args);\nusing var node = ctx.CreateNode(\"hello_world\");\nusing var sub = node.CreateSubscription\u003cTwist\u003e(\"/cmd_vel\");\nawait foreach (Twist msg in sub.ReadAllAsync())\n{\n    ...\n}\n```\n### Publishing\n```csharp\nusing var pub = node.CreatePublisher\u003cVector3\u003e(\"/vec\");\npub.Publish(new Vector3(x: 1, y: 2, z: 3));\n```\n### Handling Service Calls\n```csharp\nusing var server = node.CreateService\u003c\n    EmptyService,\n    EmptyServiceRequest,\n    EmptyServiceResponse\u003e(\"/vec\",\n        (request, state) =\u003e\n        {\n            return new EmptyServiceResponse();\n        });\nawait Task.Delay(-1);\n```\n### Calling Services\n```csharp\nusing var client = node.CreateClient\u003c\n    EmptyService,\n    EmptyServiceRequest,\n    EmptyServiceResponse\u003e(\"/vec\");\nawait client.InvokeAsync(new EmptyServiceRequest());\n``` \n### Monitoring ROS Graph Changes\n```csharp\nnode.Graph\n    .OfType\u003cNodeAppearedEvent\u003e()\n    .Subscribe(x =\u003e\n    {\n        Console.WriteLine($\"Node {x.Node.Name} is online.\");\n    });\n\nawait node.Graph.WaitForServiceServerAsync(\"/my/service\");\n```\n### Calling Action Servers\n```csharp\nusing var client = node.CreateActionClient\u003c\n    SpinAction,\n    SpinActionGoal,\n    SpinActionResult,\n    SpinActionFeedback\u003e(\"/spin\");\n\nusing var goal = await client.SendGoalAsync(\n        new SpinActionGoal(targetYaw: Math.PI));\n\nawait foreach (var feedback in goal.ReadFeedbacksAsync())\n{\n    Console.WriteLine(feedback.AngularDistanceTraveled);\n}\n\nvar result = await goal.GetResultAsync();\n```\n### Zero (Managed Heap) Allocation APIs\n```csharp\nusing var sub = node.CreateNativeSubscription\u003cTwist\u003e(\"/cmd_vel\");\nawait foreach (RosMessageBuffer msg in sub.ReadAllAsync())\n{\n    using (msg) ProcessMessage(msg);\n\n    static void ProcessMessage(RosMessageBuffer buffer)\n    {\n        ref var twist = ref buffer.AsRef\u003cTwist.Priv\u003e();\n        ...\n    }\n}\n```\n\n## Asynchronous Execution Model\nUnlike rclcpp and rclpy, rclnet doesn't have the concept of executors. Each `RclContext` runs its\nown event loop for waiting on signals and dispatching callbacks, which is essentialy a single-threaded\nexecutor.\n\nAlthough rclnet does not provide multi-threaded executors, it doesn't mean that you can't process messages or handle\nservice requests using multiple threads. All communication primitives in rclnet provide both synchronous\nand asynchronous APIs for different needs and scenarios.\n\nSynchronous APIs are simpler and faster if the work need to be done is simple enough, e.g. neither CPU-intensive nor needs to issue blocking calls. Asynchronous APIs, in contrast, are for scenarios where you need to perform asynchronous calls or\noffload blocking operations into background threads.\n\nTake subscriptions for example, you can receive messages synchronously using `IRclSubscription\u003cT\u003e.Subscribe`,\nor asynchronously using `IRclSubscription\u003cT\u003e.ReadAllAsync`. Synchronous subscriptions always handle messages\non the event loop. While for asynchronous subscriptions, you can choose where you'd like to process the received\nmessages:\n\n```csharp\nawait foreach (var msg in sub.ReadAllAsync())\n{\n    // Perform asynchronous operation.\n    await SomeAsyncOperation(msg);\n\n    // Perform synchronous operation and wait for its completion without blocking the event loop.\n    await Task.Run(() =\u003e SomeOffloadedSyncOperation(msg));\n}\n```\n\nIn the above example, the event loop of the `RclContext` is used for listening to events only. Where are the messages\nhandled depends on the `SynchronizationContext` currently captured.\n\nIf there's no `SynchronizationContext` in use, event handling happens in background threads by default. Otherwise, the\nevents will be handled in the captured `SynchronizationContext`. If you are using rclnet inside a GUI application,\nthis usually means that the events are handled on the UI thread.\n\n`RclContext`s can also have their own `SynchronizationContext`s, which always schedule asynchronous operations on the event loop.\nThis is extremely helpful if you want to introduce single-threaded concurrency into your application:\n\n```csharp\nawait using var context = new RclContext(useSynchronizationContext: true);\n\n...\n\n// Enforce execution on the event loop so that we can capture its SynchronizationContext.\nawait context.Yield();\n\n// All following awaits will resume on the event loop by default.\nawait foreach (var msg in sub.ReadAllAsync())\n{\n    // On event loop.\n    await SomeAsyncOperation(msg);\n    // On event loop.\n    await Task.Run(() =\u003e {\n        // On thread pool.\n        SomeOffloadedSyncOperation(msg);\n    });\n    // On event loop.\n    await Task.Yield();\n    // On event loop.\n\n    // We can also spin up multiple coroutines to run concurrently on the event loop.\n    Task task1 = Coroutine1Async(msg),\n         task2 = Coroutine2Async(msg);\n\n    // Or asynchronously wait for all coroutines to complete.\n    await Task.WhenAll(task1, task2);\n\n    ...\n\n    // The execution of current async method will stay on the event\n    // loop unless we break out of the SynchronizationContext using\n    // ConfigureAwait(false), or RclContext.YieldBackground().\n\n    await AnotherAsyncOperation(msg).ConfigureAwait(false);\n    // On thread pool thread.\n\n    // We can still transition back to the event loop with context.Yield().\n\n    await context.Yield();\n    // On event loop.\n\n    await RclContext.YieldBackground();\n    // On thread pool thread.\n}\n```\n\nAs shown in the above example, besides of `SynchronizationContext`, you can also use `RclContext.Yield`, `RclContext.YieldBackground` and `ConfigureAwait(false)` to perform fine-grained control\nover the asynchronous exection flow.\n\n### Additional Notes about `IRclWaitObject.WaitOneAsync`\nTimers and guard conditions created by `RclContext` implements `IRclWaitObject` interface,\nwhich allow the caller to asynchronously wait for the signal.\n\n`IRclWaitObject` interface exposes the following two overloads of `WaitOneAsync`:\n```csharp\nValueTask WaitOneAsync(bool runContinuationAsynchronously, CancellationToken cancellationToken = default);\nValueTask WaitOneAsync(CancellationToken cancellationToken = default);\n```\nThe latter overload simply calls another one with `runContinuationAsynchronously` set to `true`.\n\n`WaitOneAsync` allows the caller to explicitly control the execution of the continuation via `runContinuationAsynchronously`\nparameter. Assuming there's no captured `SynchronizationContext` or `TaskScheduler`, when `runContinuationAsynchronously`\nis set to `true`, the continuation will be scheduled to execute in thread pool. And if `runContinuationAsynchronously`\nis set to `false`, the continuation is guaranteed to execute on the event loop.\n\nHowever, when a `SynchronizationContext` or `TaskScheduler` is captured, the continuation of the call to `WaitOneAsync`\nwill always execute in the captured context, regardless of the value of `runContinuationAsynchronously`.\n\nSince context capture can be suppressed by calling `ConfigureAwait` with `continueOnCapturedContext` set to `false`,\nexecution of the continuation can be precisely controlled using `runContinuationAsynchronously` in conjunction with\n`continueOnCapturedContext`.\n\n\n| `runContinuationAsynchronously` | `continueOnCapturedContext` | Continuation Execution                                                             |\n| ------------------------------- | --------------------------- | ---------------------------------------------------------------------------------- |\n| `true`                          | `true`                      | Captured `SynchronizationContext` or `TaskScheduler` if any, thread pool otherwise |\n| `true`                          | `false`                     | Thread pool                                                                        |\n| `false`                         | `true`                      | Captured `SynchronizationContext` or `TaskScheduler` if any, event loop otherwise  |\n| `false`                         | `false`                     | Event loop                                                                         |\n\n## Building and Running Examples\n### Install dependencies\nThe following instruction assumes that you've already installed ROS 2 foxy or humble in your system.\n\nYou'll need .NET 8.0 SDK to build and run the examples, see instructions \n[here](https://learn.microsoft.com/dotnet/core/install/linux-ubuntu).\n\nMake sure you have all dependencies installed by running:\n```\nrosdep install -i --from-paths examples\n```\n\n### Run with `dotnet run`\nNow you can run example projects using `dotnet run`, e.g.\n```\ndotnet run --project examples/turtle_rotate\n```\n\n### Run with `ros2 run`\nOr you can build and install examples as colcon packages:\n```\ncolcon build --executor sequential --merge-install --paths examples/*\nsource install/setup.bash\n```\n\nTo run an example node, use `ros2 run`, e.g.\n```\nros2 run graph_monitor graph_monitor\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fnoelex%2Frclnet","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fnoelex%2Frclnet","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fnoelex%2Frclnet/lists"}