CyphalSharp 1.4.0
dotnet add package CyphalSharp --version 1.4.0
NuGet\Install-Package CyphalSharp -Version 1.4.0
<PackageReference Include="CyphalSharp" Version="1.4.0" />
<PackageVersion Include="CyphalSharp" Version="1.4.0" />
<PackageReference Include="CyphalSharp" />
paket add CyphalSharp --version 1.4.0
#r "nuget: CyphalSharp, 1.4.0"
#:package CyphalSharp@1.4.0
#addin nuget:?package=CyphalSharp&version=1.4.0
#tool nuget:?package=CyphalSharp&version=1.4.0
CyphalSharp — Cyphal / UAVCAN Library to Parse, Publish & Subscribe to Messages from Drones, Robots & Autonomous Systems in .NET
CyphalSharp is a lightweight, high-performance .NET library for parsing and publishing Cyphal (formerly UAVCAN) messages using DSDL definitions — the modern, deterministic communication standard for real-time aerospace, robotics, and automotive systems. It parses Cyphal .dsdl files at runtime, so there's no code generation — drop in a DSDL directory, call Cyphal.Initialize(), and start parsing telemetry immediately. It also constructs and encodes Cyphal packets for transmission over UDP, CAN, or any other transport, and ships a high-level CyphalNode for standard Plug-and-Play node services (Heartbeat, GetInfo, ExecuteCommand).
Cyphal powers the next generation of autonomous vehicles, robotics, industrial control, and aerospace — anywhere deterministic, decentralized, and safety-focused communication matters. Wherever there's a remote vehicle or autonomous machine producing telemetry over CAN, CAN FD, or UDP, CyphalSharp gives .NET developers a fast, reliable way to talk to it.
Table of Contents
- Features
- Supported Frameworks
- Getting Started
- Transports
- DSDL Handling
- Filtering Messages
- Code Example (High-Level UDP Transport)
- Code Example (Low-Level UDP)
- Constructing and Sending Messages
- Advanced: Asynchronous Streaming
- Plug-and-Play Node
- Example Project: CyphalConsole
- Benchmark Project
Features
- Runtime DSDL Parsing: Consumes Cyphal
.dsdlfiles at runtime. No code generation required. - Transport Agnostic: Flexible
IFrame/ITransportinterfaces supporting Cyphal/UDP, Cyphal/CAN, and more. - Cyphal/UDP Multicast & Reassembly: Robust UDP transport with transparent multi-frame transfer reassembly and timeout cleanup.
- Cyphal/CAN Support: Full CAN Classic and CAN FD support with automated tail byte handling and CRC-16-CCITT verification.
- Plug-and-Play Node Services: Built-in
CyphalNodeimplementing standard uavcan.node services (Heartbeat, GetInfo, ExecuteCommand). - Service (RPC) Support: Full support for Cyphal Service request/response patterns with the
---separator. - Union Support: Full support for Cyphal
@uniontypes with tag-based field switching. - Fixed Port ID Mapping: Supports extracting port IDs from DSDL directives or external mapping.
- Payload Validation: Built-in validation for malformed payloads with detailed error reasons.
- High Performance: Designed for speed and low allocation to handle high-throughput Cyphal streams.
- Streaming Ready: Built-in support for asynchronous, zero-allocation streaming using
ReadOnlySequence<byte>. - Cross-Platform: Can be used on any platform that supports .NET Standard 2.0 (Windows, Linux, macOS, etc.).
- Minimal Dependencies: Only requires
System.MemoryandSystem.Buffersfor modern memory-efficient parsing.
Supported Frameworks
CyphalSharp targets .NET Standard 2.0, providing broad compatibility across modern and legacy .NET platforms.
Compatible Platforms:
- .NET Core / .NET (5+): All versions.
- .NET Framework: 4.6.1 and later (4.7.2+ recommended).
- Mono: 5.4 and later.
- Xamarin.iOS / Android
- UWP: 10.0.16299 and later.
Getting Started
Using the library involves four main steps:
- Add the NuGet Package: Install the
CyphalSharppackage from NuGet into your .NET project.Install-Package CyphalSharp - Initialize the Library: At application startup, call the static
Cyphal.Initialize()method. You must specify the directory containing your DSDL files (e.g.,DSDL).Important: This step is mandatory. Calling
frame.TryParse()beforeCyphal.Initialize()will result in anInvalidOperationException. - Parse Incoming Data: Create a transport-specific frame object (e.g.,
UdpFrame) once and reuse it. As you receive data from a Cyphal stream, pass the rawbyte[]orSpan<byte>packet to theframe.TryParse()method. - Use the Result: If
TryParse()returnstrue, theframeobject will be populated with the decoded message and its fields.
Transports
CyphalSharp is designed to be extensible. Currently, it includes built-in support for:
- Cyphal/UDP: via
UdpFrameand the high-levelUdpTransportwhich handles:- Automatic IGMP/Multicast group joining based on Subject IDs.
- Transparent reassembly of multi-frame transfers.
- Port-to-Address mapping according to the Cyphal/UDP specification.
- Cyphal/CAN: via
CanFrameandCanTransportwhich handles:- 29-bit CAN ID encoding/decoding.
- Tail byte processing (SOT, EOT, Toggle, Transfer ID).
- Multi-frame reassembly with CRC-16-CCITT validation.
- Support for both CAN Classic and CAN FD payload sizes.
To support a new transport, you can implement the IFrame and ITransport interfaces or derive from the Frame base class.
DSDL Handling
The CyphalSharp library utilizes a runtime parsing mechanism for Cyphal DSDL files. This means message definitions are loaded and processed dynamically when your application starts, rather than requiring code generation. It correctly handles both Subject (Message) and Service definitions, including those with the --- Request/Response separator.
DSDL Format
Cyphal DSDL files are plain text files with the .dsdl extension. They follow the naming convention: <TypeName>.<major>.<minor>.dsdl.
Example structure:
DSDL/
uavcan/
Heartbeat.1.0.dsdl
NodeStatus.1.0.dsdl
Initializing the Library
// Initialize with your DSDL directory
Cyphal.Initialize("DSDL");
Filtering Messages
The CyphalSharp library offers flexible control over which Cyphal messages are parsed, allowing you to optimize for performance by only processing messages relevant to your application.
Initializing Message Parsing
Message filtering begins with the Cyphal.Initialize() method. This method loads message definitions from your specified DSDL directory and simultaneously defines the initial set of messages that will be processed.
The Cyphal.Initialize() method has the following signature:
public static void Initialize(string dsdlPath, params uint[] portIds)
dsdlPath: (Required) The path to the directory containing your DSDL files.portIds: (Optional) A list of specific Cyphal Port IDs (uint) you wish to enable for parsing immediately upon initialization.- If
portIdsare provided, only those specified messages will be marked for parsing. - If
portIdsare omitted (or an empty array is passed), then all messages found in the DSDL directory will be initially marked for parsing.
- If
Fine-Tuning Message Parsing (Include/Exclude)
After initialization, you can further fine-tune which messages are parsed using Cyphal.IncludeMessages() and Cyphal.ExcludeMessages(). These static methods allow you to dynamically enable or disable parsing for specific messages.
Cyphal.IncludeMessages(params uint[] messageIds):- Purpose: To enable parsing for specific Cyphal message ID(s) that were previously disabled, or to ensure they are enabled.
- Behavior: If no
messageIdsare provided then all currently loaded message definitions will be marked for parsing.
// After initialization, enable parsing for a specific message Cyphal.IncludeMessages(7509);Cyphal.ExcludeMessages(params uint[] messageIds):- Purpose: To disable parsing for specific Cyphal message ID(s).
- Behavior: The specified messages will be marked as excluded and will be ignored by
frame.TryParse().
// Disable parsing for another message Cyphal.ExcludeMessages(390);
Example Workflow:
- Scenario A: Parse only specific messages from the start:
// Initialize, loading definitions and enabling only specific message IDs Cyphal.Initialize("DSDL", 7509, 390); - Scenario B: Parse all messages from the start:
// Initialize, loading definitions and enabling ALL messages Cyphal.Initialize("DSDL"); - Scenario C: Parse most messages, but exclude a few:
Cyphal.Initialize("DSDL"); // All messages enabled initially Cyphal.ExcludeMessages(390); // Exclude a specific message
Code Example (High-Level UDP Transport)
The UdpTransport class provides a high-level API that handles networking and reassembly for you.
using CyphalSharp;
// 1. Initialize
Cyphal.Initialize("DSDL");
// 2. Create and start the transport
var transport = new UdpTransport(nodeId: 42);
await transport.StartAsync();
// 3. Subscribe to a subject (joins multicast group automatically)
transport.SubscribeToSubject(7509); // Heartbeat Subject ID
// 4. Handle received messages
transport.FrameReceived += (sender, frame) =>
{
var msgName = frame.Message?.Name ?? "Unknown";
Console.WriteLine($"Received {msgName} from {frame.SourceNodeId}");
foreach(var field in frame.Fields)
{
Console.WriteLine($" {field.Key}: {field.Value}");
}
};
Code Example (Low-Level UDP)
using CyphalSharp;
using System;
using System.Linq;
using System.Net;
using System.Net.Sockets;
// 1. Initialize the library with the DSDL directory.
Cyphal.Initialize("DSDL");
// 2. Create a UdpFrame object once and reuse it for high performance (zero allocation).
var frame = new UdpFrame();
// Example: Listen for Cyphal packets on a local UDP port.
var endpoint = new IPEndPoint(IPAddress.Loopback, 14550);
using var udpClient = new UdpClient(endpoint);
Console.WriteLine($"Listening for Cyphal packets on {endpoint}...\n");
while (true)
{
var packet = udpClient.Receive(ref endpoint);
// 3. Try to parse the packet into the existing frame object.
if (frame.TryParse(packet))
{
// 4. If successful, use the data.
var fields = string.Join(", ", frame.Fields.Select(f => $"{f.Key}: {f.Value}"));
Console.WriteLine($"Received: {Cyphal.RegisteredMessages[frame.DataSpecifierId].Name} => {fields}");
}
}
Constructing and Sending Messages
CyphalSharp makes it easy to construct Cyphal packets for transmission.
// 1. Get the message definition you want to send.
var heartbeatDef = Cyphal.RegisteredMessages.Values.First(m => m.Name == "uavcan.Heartbeat");
// 2. Create a UdpFrame and set the header information.
var frame = new UdpFrame
{
Version = 0,
Priority = 3, // Nominal
SourceNodeId = 42,
DestinationNodeId = 0xFFFF, // Broadcast
DataSpecifierId = (ushort)heartbeatDef.PortId,
TransferId = 12345,
FrameIndex = 0,
EndOfTransfer = true,
Message = heartbeatDef
};
// 3. Set the field values.
var values = new Dictionary<string, object>
{
{ "uptime", (uint)1234 },
{ "health", (byte)0 },
{ "mode", (byte)0 },
{ "vendor_specific_status_code", (byte)42 }
};
frame.SetFields(values);
// 4. Serialize to a byte array.
byte[] packet = frame.ToBytes();
// 5. Send over your transport (e.g., UDP).
udpClient.Send(packet, packet.Length, remoteEndPoint);
Advanced: Asynchronous Streaming
For high-bandwidth or fragmented streams, CyphalSharp supports ReadOnlySequence<byte>. This allows for efficient, asynchronous parsing without manual buffer management.
public void ProcessStream(ReadOnlySequence<byte> buffer)
{
var frame = new UdpFrame();
while (frame.TryParse(buffer, out SequencePosition consumed, out SequencePosition examined))
{
Console.WriteLine($"Parsed Message ID: {frame.DataSpecifierId}");
buffer = buffer.Slice(consumed);
}
}
Quick Start: Send a Heartbeat
Cyphal.Initialize("DSDL"); // Load DSDL definitions
var hbDef = Cyphal.RegisteredMessages.Values.First(m => m.Name.Contains("Heartbeat"));
var frame = new UdpFrame { SourceNodeId = 42, DataSpecifierId = (ushort)hbDef.PortId, Priority = 3, EndOfTransfer = true };
frame.SetFields(new Dictionary<string, object> { { "uptime", (uint)0 }, { "health", (byte)0 }, { "mode", (byte)0 }, { "vendor_specific_status_code", (byte)0 } });
await udpClient.SendAsync(frame.ToBytes(), new IPEndPoint(IPAddress.Parse("239.0.0.1"), 14550)); // Send to Heartbeat multicast
Plug-and-Play Node Example
The CyphalNode class provides a simple way to implement standard Cyphal node services (Heartbeat, GetInfo, ExecuteCommand).
using CyphalSharp;
// 1. Initialize the library with DSDL definitions
Cyphal.Initialize("DSDL");
// 2. Create a transport (UDP or CAN)
var transport = new UdpTransport(nodeId: 42);
// 3. Create and configure the node
var node = new CyphalNode(42, transport)
{
Name = "MyCyphalNode",
Health = 0, // HEALTH_NOMINAL
Mode = 1 // MODE_INITIALIZING
};
// 4. Optional: Handle ExecuteCommand requests
node.ExecuteCommandReceived += (sender, args) =>
{
Console.WriteLine($"Received command: {args.Command}");
// Set args.Result to 0 for SUCCESS, or other values for failure
args.Result = 0;
};
// 5. Start the node (begins publishing Heartbeat, responds to GetInfo/ExecuteCommand)
await node.StartAsync();
// Node is now operational...
// When done:
node.Stop();
node.Dispose();
Example Project: CyphalConsole
The CyphalConsole project serves as a practical example demonstrating how to use the CyphalSharp library for both sending and receiving Cyphal messages over UDP, all within a single console application. It's particularly useful for testing, development, and quickly observing Cyphal communication.
CyphalConsole(Transmitter & Receiver): This console application runs two concurrent tasks:- Transmitter (Tx): Generates and sends synthetic Cyphal messages (e.g., Heartbeat) over UDP to the default Cyphal port (UDP 14550). It showcases how to construct Cyphal
UdpFrameobjects and serialize them into byte arrays for transmission. - Receiver (Rx): Listens for incoming Cyphal UDP packets on the default Cyphal port (UDP 14550). It demonstrates how to parse raw byte arrays into
UdpFrameobjects usingframe.TryParse()and access the decoded message fields. Tx and Rx are displayed in separate halves of the screen.
- Transmitter (Tx): Generates and sends synthetic Cyphal messages (e.g., Heartbeat) over UDP to the default Cyphal port (UDP 14550). It showcases how to construct Cyphal
This example provides a quick way to:
- Test your CyphalSharp integration: Verify that your application can correctly send and receive messages.
- Debug Cyphal communication: Simulate both a Cyphal source and a listener within one application.
- Understand basic usage: See concrete implementations of Cyphal message handling.
To run this example:
- Navigate to the
CyphalConsoleproject directory in your terminal. - Run the project using
dotnet run.- You will see both
Tx =>(transmitted) andRx =>(received) messages in the same terminal.
- You will see both
Benchmark Project: CyphalSharp.Benchmark
The CyphalSharp.Benchmark project is a dedicated suite for measuring the performance characteristics of the CyphalSharp library. It leverages BenchmarkDotNet to provide accurate and reliable performance metrics for critical operations.
Benchmarks currently included:
- CRC Calculation: Measures the speed of
Crc.Calculate(). - Message Parsing: Evaluates the performance of
frame.TryParse(). - Cyphal Initialization: Measures the loading and parsing time of Cyphal DSDL files.
To run the benchmarks:
- Navigate to the
CyphalSharp.Benchmarkproject directory in your terminal. - Run the project in Release mode (essential for accurate results) using the following command:
Thedotnet run -c Release --project CyphalSharp.Benchmark.csproj -- --filter *--filter *argument ensures all benchmarks within the project are executed. BenchmarkDotNet will produce detailed reports in theBenchmarkDotNet.Artifacts/resultsdirectory.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net5.0 was computed. net5.0-windows was computed. net6.0 was computed. net6.0-android was computed. net6.0-ios was computed. net6.0-maccatalyst was computed. net6.0-macos was computed. net6.0-tvos was computed. net6.0-windows was computed. net7.0 was computed. net7.0-android was computed. net7.0-ios was computed. net7.0-maccatalyst was computed. net7.0-macos was computed. net7.0-tvos was computed. net7.0-windows was computed. net8.0 was computed. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 was computed. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
| .NET Core | netcoreapp2.0 was computed. netcoreapp2.1 was computed. netcoreapp2.2 was computed. netcoreapp3.0 was computed. netcoreapp3.1 was computed. |
| .NET Standard | netstandard2.0 is compatible. netstandard2.1 was computed. |
| .NET Framework | net461 was computed. net462 was computed. net463 was computed. net47 was computed. net471 was computed. net472 was computed. net48 was computed. net481 was computed. |
| MonoAndroid | monoandroid was computed. |
| MonoMac | monomac was computed. |
| MonoTouch | monotouch was computed. |
| Tizen | tizen40 was computed. tizen60 was computed. |
| Xamarin.iOS | xamarinios was computed. |
| Xamarin.Mac | xamarinmac was computed. |
| Xamarin.TVOS | xamarintvos was computed. |
| Xamarin.WatchOS | xamarinwatchos was computed. |
-
.NETStandard 2.0
- System.Buffers (>= 4.6.1)
- System.Memory (>= 4.6.3)
-
net10.0
- No dependencies.
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
- Added standard Cyphal well-known port IDs (Heartbeat=7509, GetInfo=430, ExecuteCommand=435).
- Structured error handling with graceful degradation and central diagnostics counters.
- Introduced a dependency-free ILogger abstraction for diagnostics output.
- Improved NuGet packaging and build targets for DSDL content delivery.
- Expanded XML documentation coverage across the public API.