An open API service indexing awesome lists of open source software.

https://github.com/dborgards/eds-dcf-net

C# .NET CANopen library for EDS, DCF, CPJ, XDD and XDC (CiA 306 / CiA 311)
https://github.com/dborgards/eds-dcf-net

canopen cia csharp csharp-library dcf dotnet dotnet-core eds fieldbus industrial-automation xdc xdd

Last synced: about 2 months ago
JSON representation

C# .NET CANopen library for EDS, DCF, CPJ, XDD and XDC (CiA 306 / CiA 311)

Awesome Lists containing this project

README

          

# EdsDcfNet

[![Build Status](https://github.com/dborgards/eds-dcf-net/actions/workflows/build.yml/badge.svg)](https://github.com/dborgards/eds-dcf-net/actions/workflows/build.yml)
[![Semantic Release](https://img.shields.io/badge/semantic--release-conventionalcommits-e10079?logo=semantic-release)](https://github.com/semantic-release/semantic-release)
[![NuGet Version](https://img.shields.io/nuget/v/EdsDcfNet)](https://www.nuget.org/packages/EdsDcfNet)
[![NuGet Downloads](https://img.shields.io/nuget/dt/EdsDcfNet)](https://www.nuget.org/packages/EdsDcfNet)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
[![codecov](https://codecov.io/gh/dborgards/eds-dcf-net/branch/main/graph/badge.svg)](https://codecov.io/gh/dborgards/eds-dcf-net)

A comprehensive, easy-to-use C# .NET library for CANopen file formats:
CiA DS 306 (EDS, DCF, CPJ) and CiA 311 (XDD, XDC).

## Features

โœจ **Simple API** - Intuitive, fluent API style for quick integration

๐Ÿ“– **Read & Write EDS** - Parse and generate Electronic Data Sheets

๐Ÿ“ **Read & Write DCF** - Process and create Device Configuration Files

๐ŸŒ **Read & Write CPJ** - Parse and create Nodelist Project files (CiA 306-3 network topologies)

๐Ÿงฉ **Read & Write XDD/XDC** - Parse and generate CiA 311 XML device descriptions/configurations

๐Ÿ”„ **EDS to DCF Conversion** - Easy conversion with configuration parameters

๐ŸŽฏ **Type-Safe** - Fully typed models for all CANopen objects

๐Ÿ“ฆ **Modular** - Support for modular devices (bus couplers + modules)

โœ… **CiA DS 306 v1.4 / CiA 311 v1.1 Compliant** - Implemented according to official specification

## Quick Start

### Reading an EDS File

```csharp
using EdsDcfNet;

// Read EDS file
var eds = CanOpenFile.ReadEds("device.eds");

// Display device information
Console.WriteLine($"Device: {eds.DeviceInfo.ProductName}");
Console.WriteLine($"Vendor: {eds.DeviceInfo.VendorName}");
Console.WriteLine($"Product Number: 0x{eds.DeviceInfo.ProductNumber:X}");
```

### Writing an EDS File

```csharp
using EdsDcfNet;

var eds = CanOpenFile.ReadEds("device.eds");
eds.FileInfo.FileRevision++;
CanOpenFile.WriteEds(eds, "device_updated.eds");
```

### Async File I/O (`async`/`await`)

```csharp
using EdsDcfNet;
using System.Threading;

using var cts = new CancellationTokenSource();

var eds = await CanOpenFile.ReadEdsAsync("device.eds", cts.Token);
eds.FileInfo.FileRevision++;
await CanOpenFile.WriteEdsAsync(eds, "device_updated.eds", cts.Token);
```

### Stream-based I/O

```csharp
using EdsDcfNet;
using System.IO;

using var stream = File.OpenRead("device.eds");
var eds = CanOpenFile.ReadEds(stream);

using var outStream = new MemoryStream();
CanOpenFile.WriteEds(eds, outStream);
```

> Stream ownership: stream overloads do **not** dispose input/output streams.
> The caller remains responsible for stream lifetime.

## Output Encoding Policy

All writer APIs that persist text (`WriteEds*`, `WriteDcf*`, `WriteCpj*`, `WriteXdd*`, `WriteXdc*`)
write **UTF-8 without BOM** by default for file and stream output.

This is an intentional interoperability choice:
- CiA DS 306 is historically ASCII-oriented.
- UTF-8 keeps full ASCII compatibility for 7-bit content.
- UTF-8 also preserves non-ASCII characters in names/comments instead of replacing them.

### Guidance for strict ASCII toolchains

If a downstream tool only accepts strict ASCII, keep model text in 7-bit ASCII characters,
or transcode explicitly to strict ASCII at your boundary and fail fast on non-ASCII content.

```csharp
using EdsDcfNet;
using System.IO;
using System.Text;

var asciiStrict = Encoding.GetEncoding(
"us-ascii",
EncoderFallback.ExceptionFallback,
DecoderFallback.ExceptionFallback);

var dcf = CanOpenFile.ReadDcf("device.dcf");
var text = CanOpenFile.WriteDcfToString(dcf);
File.WriteAllText("device_ascii.dcf", text, asciiStrict);
```

### Reading an XDD File (CiA 311 XML)

```csharp
using EdsDcfNet;

// Read XDD file
var xdd = CanOpenFile.ReadXdd("device.xdd");

Console.WriteLine($"Device: {xdd.DeviceInfo.ProductName}");
Console.WriteLine($"Vendor: {xdd.DeviceInfo.VendorName}");
```

### Reading a DCF File

```csharp
using EdsDcfNet;

// Read DCF file
var dcf = CanOpenFile.ReadDcf("configured_device.dcf");

Console.WriteLine($"Node ID: {dcf.DeviceCommissioning.NodeId}");
Console.WriteLine($"Baudrate: {dcf.DeviceCommissioning.Baudrate} kbit/s");
```

### Reading an XDC File (CiA 311 XML)

```csharp
using EdsDcfNet;

// Read XDC file
var xdc = CanOpenFile.ReadXdc("configured_device.xdc");

Console.WriteLine($"Node ID: {xdc.DeviceCommissioning.NodeId}");
Console.WriteLine($"Baudrate: {xdc.DeviceCommissioning.Baudrate} kbit/s");
```

### Working with ApplicationProcess (CiA 311 ยง6.4.5)

XDD/XDC files may include an `ApplicationProcess` element describing device parameters
at the application level. The typed model gives full programmatic access to all
sub-constructs.

```csharp
using EdsDcfNet;

var xdd = CanOpenFile.ReadXdd("device.xdd");

if (xdd.ApplicationProcess is { } ap)
{
// Iterate parameters
foreach (var param in ap.ParameterList)
{
var displayName = param.LabelGroup.GetDisplayName() ?? param.UniqueId;
Console.WriteLine($"Parameter: {displayName}");
}

// Inspect data type definitions
if (ap.DataTypeList is { } dtl)
{
foreach (var enumType in dtl.Enums)
Console.WriteLine($"Enum type: {enumType.Name}");
}
}
```

### Converting EDS to DCF

```csharp
using EdsDcfNet;

// Read EDS
var eds = CanOpenFile.ReadEds("device.eds");

// Convert to DCF with node ID and baudrate
var dcf = CanOpenFile.EdsToDcf(eds, nodeId: 2, baudrate: 500, nodeName: "MyDevice");

// Save DCF
CanOpenFile.WriteDcf(dcf, "device_node2.dcf");
```

### Validating models before write operations

Use the validation API to detect invalid commissioning values and inconsistent
object-list definitions before serializing files.

```csharp
using EdsDcfNet;
using EdsDcfNet.Validation;

var dcf = CanOpenFile.ReadDcf("configured_device.dcf");

IReadOnlyList issues = CanOpenFile.Validate(dcf);
if (issues.Count > 0)
{
foreach (var issue in issues)
Console.WriteLine(issue);
}
```

`CanOpenFile.Validate(...)` is the recommended entry point and routes to the
full model validator, returning path-based `ValidationIssue` entries.
Current checks include:

- commissioning constraints (Node-ID range `1..127` for commissioned nodes; `NodeId == 0` is accepted only when commissioning is omitted, baudrate range with `0` accepted for that omitted state, key string limits)
- device info constraints (name/order-code length, granularity limit)
- object dictionary consistency (list membership, duplicates, missing entries)
- object-level constraints (object type validity, parameter-name length, SubNumber mismatch)

### Working with Nodelist Projects (CPJ)

```csharp
using EdsDcfNet;
using EdsDcfNet.Models;

// Read a CPJ file describing the network topology
var cpj = CanOpenFile.ReadCpj("nodelist.cpj");

foreach (var network in cpj.Networks)
{
Console.WriteLine($"Network: {network.NetName}");
foreach (var node in network.Nodes.Values)
{
Console.WriteLine($" Node {node.NodeId}: {node.Name} ({node.DcfFileName})");
}
}

// Create a new CPJ
var project = new NodelistProject();
project.Networks.Add(new NetworkTopology
{
NetName = "Production Line 1",
Nodes =
{
[2] = new NetworkNode { NodeId = 2, Present = true, Name = "PLC", DcfFileName = "plc.dcf" },
[3] = new NetworkNode { NodeId = 3, Present = true, Name = "IO Module", DcfFileName = "io.dcf" }
}
});
CanOpenFile.WriteCpj(project, "network.cpj");
```

### Working with Object Dictionary

```csharp
using EdsDcfNet.Extensions;

var dcf = CanOpenFile.ReadDcf("device.dcf");

// Get object
var deviceType = dcf.ObjectDictionary.GetObject(0x1000);

// Set value (returns true if object exists, false if not found)
bool set = dcf.ObjectDictionary.SetParameterValue(0x1000, "0x00000191");

// Browse PDO objects
var tpdos = dcf.ObjectDictionary.GetPdoCommunicationParameters(transmit: true);
```

## API Overview

### Main Class: `CanOpenFile`

Writer encoding note: all file/stream `Write*` APIs use UTF-8 without BOM.

```csharp
// Read EDS
ElectronicDataSheet ReadEds(string filePath)
ElectronicDataSheet ReadEds(string filePath, long maxInputSize)
Task ReadEdsAsync(string filePath, CancellationToken cancellationToken = default)
Task ReadEdsAsync(string filePath, long maxInputSize, CancellationToken cancellationToken = default)
ElectronicDataSheet ReadEdsFromString(string content)
ElectronicDataSheet ReadEdsFromString(string content, long maxInputSize)
ElectronicDataSheet ReadEds(Stream stream)
ElectronicDataSheet ReadEds(Stream stream, long maxInputSize)
Task ReadEdsAsync(Stream stream, CancellationToken cancellationToken = default)
Task ReadEdsAsync(Stream stream, long maxInputSize, CancellationToken cancellationToken = default)

// Write EDS
void WriteEds(ElectronicDataSheet eds, string filePath)
void WriteEds(ElectronicDataSheet eds, Stream stream)
Task WriteEdsAsync(ElectronicDataSheet eds, string filePath, CancellationToken cancellationToken = default)
Task WriteEdsAsync(ElectronicDataSheet eds, Stream stream, CancellationToken cancellationToken = default)
string WriteEdsToString(ElectronicDataSheet eds)

// Read DCF
DeviceConfigurationFile ReadDcf(string filePath)
DeviceConfigurationFile ReadDcf(string filePath, long maxInputSize)
Task ReadDcfAsync(string filePath, CancellationToken cancellationToken = default)
Task ReadDcfAsync(string filePath, long maxInputSize, CancellationToken cancellationToken = default)
DeviceConfigurationFile ReadDcfFromString(string content)
DeviceConfigurationFile ReadDcfFromString(string content, long maxInputSize)
DeviceConfigurationFile ReadDcf(Stream stream)
DeviceConfigurationFile ReadDcf(Stream stream, long maxInputSize)
Task ReadDcfAsync(Stream stream, CancellationToken cancellationToken = default)
Task ReadDcfAsync(Stream stream, long maxInputSize, CancellationToken cancellationToken = default)

// Write DCF
void WriteDcf(DeviceConfigurationFile dcf, string filePath)
void WriteDcf(DeviceConfigurationFile dcf, Stream stream)
Task WriteDcfAsync(DeviceConfigurationFile dcf, string filePath, CancellationToken cancellationToken = default)
Task WriteDcfAsync(DeviceConfigurationFile dcf, Stream stream, CancellationToken cancellationToken = default)
string WriteDcfToString(DeviceConfigurationFile dcf)

// Read CPJ (CiA 306-3 Nodelist Project)
NodelistProject ReadCpj(string filePath)
NodelistProject ReadCpj(string filePath, long maxInputSize)
Task ReadCpjAsync(string filePath, CancellationToken cancellationToken = default)
Task ReadCpjAsync(string filePath, long maxInputSize, CancellationToken cancellationToken = default)
NodelistProject ReadCpjFromString(string content)
NodelistProject ReadCpjFromString(string content, long maxInputSize)
NodelistProject ReadCpj(Stream stream)
NodelistProject ReadCpj(Stream stream, long maxInputSize)
Task ReadCpjAsync(Stream stream, CancellationToken cancellationToken = default)
Task ReadCpjAsync(Stream stream, long maxInputSize, CancellationToken cancellationToken = default)

// Write CPJ
void WriteCpj(NodelistProject cpj, string filePath)
void WriteCpj(NodelistProject cpj, Stream stream)
Task WriteCpjAsync(NodelistProject cpj, string filePath, CancellationToken cancellationToken = default)
Task WriteCpjAsync(NodelistProject cpj, Stream stream, CancellationToken cancellationToken = default)
string WriteCpjToString(NodelistProject cpj)

// Read XDD (CiA 311 XML Device Description)
ElectronicDataSheet ReadXdd(string filePath)
ElectronicDataSheet ReadXdd(string filePath, long maxInputSize)
Task ReadXddAsync(string filePath, CancellationToken cancellationToken = default)
Task ReadXddAsync(string filePath, long maxInputSize, CancellationToken cancellationToken = default)
ElectronicDataSheet ReadXddFromString(string content)
ElectronicDataSheet ReadXddFromString(string content, long maxInputSize)
ElectronicDataSheet ReadXdd(Stream stream)
ElectronicDataSheet ReadXdd(Stream stream, long maxInputSize)
Task ReadXddAsync(Stream stream, CancellationToken cancellationToken = default)
Task ReadXddAsync(Stream stream, long maxInputSize, CancellationToken cancellationToken = default)

// Write XDD
void WriteXdd(ElectronicDataSheet xdd, string filePath)
void WriteXdd(ElectronicDataSheet xdd, Stream stream)
Task WriteXddAsync(ElectronicDataSheet xdd, string filePath, CancellationToken cancellationToken = default)
Task WriteXddAsync(ElectronicDataSheet xdd, Stream stream, CancellationToken cancellationToken = default)
string WriteXddToString(ElectronicDataSheet xdd)

// Read XDC (CiA 311 XML Device Configuration)
DeviceConfigurationFile ReadXdc(string filePath)
DeviceConfigurationFile ReadXdc(string filePath, long maxInputSize)
Task ReadXdcAsync(string filePath, CancellationToken cancellationToken = default)
Task ReadXdcAsync(string filePath, long maxInputSize, CancellationToken cancellationToken = default)
DeviceConfigurationFile ReadXdcFromString(string content)
DeviceConfigurationFile ReadXdcFromString(string content, long maxInputSize)
DeviceConfigurationFile ReadXdc(Stream stream)
DeviceConfigurationFile ReadXdc(Stream stream, long maxInputSize)
Task ReadXdcAsync(Stream stream, CancellationToken cancellationToken = default)
Task ReadXdcAsync(Stream stream, long maxInputSize, CancellationToken cancellationToken = default)

// Write XDC
void WriteXdc(DeviceConfigurationFile xdc, string filePath)
void WriteXdc(DeviceConfigurationFile xdc, Stream stream)
Task WriteXdcAsync(DeviceConfigurationFile xdc, string filePath, CancellationToken cancellationToken = default)
Task WriteXdcAsync(DeviceConfigurationFile xdc, Stream stream, CancellationToken cancellationToken = default)
string WriteXdcToString(DeviceConfigurationFile xdc)

// Validate models
IReadOnlyList Validate(ElectronicDataSheet eds)
IReadOnlyList Validate(DeviceConfigurationFile dcf)

// Convert EDS to DCF
DeviceConfigurationFile EdsToDcf(ElectronicDataSheet eds, byte nodeId,
ushort baudrate = 250, string? nodeName = null)
```

### Input Size Limits and Tuning

All read APIs apply a safe default input-size limit of **10 MB**
(`IniParser.DefaultMaxInputSize`) to reduce denial-of-service risk from
unexpectedly large payloads.

You can override this limit per operation when you need to process larger files:

```csharp
var xdd = CanOpenFile.ReadXdd("large-device.xdd", maxInputSize: 50L * 1024 * 1024);
```

Guidance:
- Keep the default whenever possible.
- Increase limits only for trusted sources and known use cases.
- Set the limit just high enough for your expected maximum file size.

## Supported Features

- โœ… Complete EDS parsing and writing
- โœ… Complete DCF parsing and writing
- โœ… CPJ nodelist project parsing and writing (CiA 306-3 network topologies)
- โœ… XDD parsing and writing (CiA 311 XML device description)
- โœ… XDC parsing and writing (CiA 311 XML device configuration)
- โœ… All Object Types (NULL, DOMAIN, DEFTYPE, DEFSTRUCT, VAR, ARRAY, RECORD)
- โœ… Sub-objects and sub-indexes
- โœ… Compact Storage (CompactSubObj, CompactPDO)
- โœ… Object Links
- โœ… Modular device concept
- โœ… Hexadecimal, decimal, and octal numbers
- โœ… $NODEID formula evaluation (e.g., $NODEID+0x200)
- โœ… CANopen Safety (EN 50325-5) - SRDOMapping, InvertedSRAD
- โœ… Comments and additional sections

## Error Handling

Writer APIs expose format-specific exceptions with context:

- `EdsWriter` / `CanOpenFile.WriteEds*`: `EdsWriteException`
- `DcfWriter` / `CanOpenFile.WriteDcf*`: `DcfWriteException`
- `CpjWriter` / `CanOpenFile.WriteCpj*`: `CpjWriteException`
- `XddWriter` / `CanOpenFile.WriteXdd*`: `XddWriteException`
- `XdcWriter` / `CanOpenFile.WriteXdc*`: `XdcWriteException`

When a failure can be attributed to a concrete generated section/element,
the exception contains a `SectionName` value (for example `DeviceInfo`,
`Topology`, `DeviceProfile`, or `deviceCommissioning`).

## Examples

Complete examples can be found in the `examples/EdsDcfNet.Examples` project.

## Performance Benchmarks

A dedicated BenchmarkDotNet project is available at:

- `benchmarks/EdsDcfNet.Benchmarks`

Run all benchmarks:

```bash
dotnet run -c Release -p benchmarks/EdsDcfNet.Benchmarks -- --filter "*"
```

Baseline scenario definitions and artifact locations are documented in:

- `benchmarks/EdsDcfNet.Benchmarks/BASELINE.md`

## Project Structure

```
eds-dcf-net/
โ”œโ”€โ”€ src/
โ”‚ โ””โ”€โ”€ EdsDcfNet/ # Main library
โ”‚ โ”œโ”€โ”€ Models/ # Data models
โ”‚ โ”œโ”€โ”€ Parsers/ # EDS/DCF/CPJ/XDD/XDC parsers
โ”‚ โ”œโ”€โ”€ Writers/ # EDS/DCF/CPJ/XDD/XDC writers
โ”‚ โ”œโ”€โ”€ Utilities/ # Helper classes
โ”‚ โ”œโ”€โ”€ Exceptions/ # Custom exceptions
โ”‚ โ””โ”€โ”€ Extensions/ # Extension methods
โ”œโ”€โ”€ benchmarks/
โ”‚ โ””โ”€โ”€ EdsDcfNet.Benchmarks/ # BenchmarkDotNet throughput/memory benchmarks
โ”œโ”€โ”€ examples/
โ”‚ โ””โ”€โ”€ EdsDcfNet.Examples/ # Example application
โ””โ”€โ”€ docs/
โ”œโ”€โ”€ architecture/ # ARC42 software architecture
โ””โ”€โ”€ cia/ # CiA DS 306 specification
```

## Requirements

**For consuming the NuGet package:**

- Any .NET implementation compatible with .NET Standard 2.0
(e.g., .NET Framework 4.6.1+, .NET Core 2.0+, .NET 5+, Unity, Xamarin)

**For building this repository (library, tests, examples):**

- .NET SDK 10.0 or higher
- C# 13.0 (as provided by the .NET 10 SDK)

## License

MIT License - see [LICENSE](LICENSE) file

## Specification

Based on:
- **CiA DS 306 Version 1.4.0** (December 15, 2021)
- **CiA 311** XML device description/configuration concepts (XDD/XDC)

## Support

For questions or issues:
- GitHub Issues: https://github.com/dborgards/eds-dcf-net/issues

---

**EdsDcfNet** - Professional CANopen EDS/DCF/CPJ/XDD/XDC processing in C# .NET