SiLA2.Client.Dynamic 10.2.2

dotnet add package SiLA2.Client.Dynamic --version 10.2.2
                    
NuGet\Install-Package SiLA2.Client.Dynamic -Version 10.2.2
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="SiLA2.Client.Dynamic" Version="10.2.2" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="SiLA2.Client.Dynamic" Version="10.2.2" />
                    
Directory.Packages.props
<PackageReference Include="SiLA2.Client.Dynamic" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add SiLA2.Client.Dynamic --version 10.2.2
                    
#r "nuget: SiLA2.Client.Dynamic, 10.2.2"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package SiLA2.Client.Dynamic@10.2.2
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=SiLA2.Client.Dynamic&version=10.2.2
                    
Install as a Cake Addin
#tool nuget:?package=SiLA2.Client.Dynamic&version=10.2.2
                    
Install as a Cake Tool

SiLA2.Client.Dynamic

Introduction

SiLA2.Client.Dynamic is a .NET 10 package that enables runtime-based client development for SiLA2 servers without requiring compile-time generated gRPC stubs. This package allows developers to build generic SiLA2 client applications that can discover servers on the network, introspect their capabilities dynamically, and invoke commands and properties without pre-generating client code from .proto files.

What is SiLA2?

SiLA2 (Standardization in Lab Automation) is an open connectivity standard for laboratory instruments and automation systems. It uses gRPC for communication and defines a Feature Definition Language (FDL) for describing device capabilities in XML format.

Why SiLA2.Client.Dynamic?

Traditional SiLA2 client development requires:

  1. Obtaining .proto files for each feature
  2. Generating client stubs at compile time using protoc
  3. Rebuilding your application whenever features change

SiLA2.Client.Dynamic eliminates these requirements by:

  • Discovering servers and features at runtime via mDNS
  • Retrieving FDL definitions dynamically from servers
  • Generating protobuf types and gRPC calls on-the-fly using reflection
  • Adapting to server capabilities without recompilation

When to Use SiLA2.Client.Dynamic

Use this package when:

  • Building generic SiLA2 client tools (e.g., Universal Client, testing frameworks)
  • The server's feature set is unknown at compile time
  • Rapid prototyping without code generation overhead
  • Creating discovery tools or server monitoring applications
  • Testing multiple servers with different feature sets

Use standard SiLA2.Client when:

  • You know the exact features your client will use
  • You want compile-time type safety and IntelliSense support
  • Performance is critical (dynamic invocation has slight overhead)
  • You're building a production client for specific devices

Key Features

  • Automatic Server Discovery: Find SiLA2 servers on the network via mDNS/DNS-SD
  • Runtime Feature Introspection: Retrieve and parse FDL definitions dynamically
  • Dynamic Protobuf Generation: Generate message types at runtime without .proto files
  • Full SiLA2 Support: Unobservable and observable commands, properties, and subscriptions
  • Type Flexibility: Work with dynamic types or cast to known protobuf types as needed
  • Minimal Configuration: Simple appsettings.json setup with network interface selection

Installation

NuGet Package Manager Console

Install-Package SiLA2.Client.Dynamic

.NET CLI

dotnet add package SiLA2.Client.Dynamic

Prerequisites


Quick Start

1. Create Configuration File

Add appsettings.json to your project:

{
  "DetailedErrors": true,
  "Logging": {
    "LogLevel": {
      "Default": "Debug",
      "System": "Information",
      "Grpc": "Information",
      "Microsoft": "Information"
    }
  },
  "Connection": {
    "FQHN": "localhost",
    "Port": 50052,
    "ServerDiscovery": {
      "NIC": "",
      "ServiceName": "_sila._tcp"
    }
  }
}

Configuration Options:

  • FQHN: Fallback IP address, CIDR notation, or fully qualified hostname
  • Port: Default port for manual connections (standard SiLA2 port is 50052)
  • ServerDiscovery.NIC: Network interface name (e.g., "Ethernet", "eth0", "WLAN0"). Leave empty to use default interface
  • ServerDiscovery.ServiceName: mDNS service type suffix (standard is "_sila._tcp")

2. Initialize the Client

using Microsoft.Extensions.Configuration;
using SiLA2.Client.Dynamic;
using SiLA2.Network.Discovery.mDNS;

// Load configuration
var configBuilder = new ConfigurationBuilder()
    .SetBasePath(Directory.GetCurrentDirectory())
    .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true);
var configuration = configBuilder.Build();

// Create dynamic configurator
var client = new DynamicConfigurator(configuration, args);

3. Discover Servers and Features

Option A: Automatic Discovery via mDNS

// Search for servers on the network and retrieve their features
var serverFeatureMap = await client.GetFeatures();

// List discovered servers
foreach (var server in client.ServerFeatureMap)
{
    Console.WriteLine($"Server: {server.Key.ServerName} at {server.Key.Address}:{server.Key.Port}");
    foreach (var feature in server.Value)
    {
        Console.WriteLine($"  - {feature.Key}");
    }
}

Option B: Manual Connection (Known Server)

// Connect to a specific server
var connectionInfo = new ConnectionInfo("127.0.0.1", 50052);
var serverFeatureMap = await client.GetFeatures(new[] { connectionInfo });

4. Invoke a Simple Property

using Grpc.Net.Client;

// Get a gRPC channel to the server
var connectionInfo = client.ServerFeatureMap.Keys.First();
var channel = await client.GetChannel(connectionInfo.Address, connectionInfo.Port);

// Find the SiLAService core feature
var silaServiceFeature = client.ServerFeatureMap[connectionInfo]
    .Values.Single(x => x.FullyQualifiedIdentifier == "org.silastandard/core/SiLAService/v1");

// Call the ServerUUID property
dynamic serverUUIDResponse = client.DynamicMessageService
    .GetUnobservableProperty("ServerUUID", channel, silaServiceFeature);

Console.WriteLine($"Server UUID: {serverUUIDResponse.ServerUUID.Value}");

Architecture Overview

How It Works

SiLA2.Client.Dynamic follows this workflow:

1. Server Discovery (mDNS)
   └─> Finds servers announcing "_sila._tcp" service

2. Feature Retrieval (gRPC call to SiLAService.Get_ImplementedFeatures)
   └─> Gets list of feature identifiers (FQIs)

3. FDL Definition Retrieval (gRPC call to SiLAService.GetFeatureDefinition)
   └─> Downloads XML feature definitions for each feature

4. Dynamic Type Generation (Runtime IL Emission)
   └─> PayloadFactory generates protobuf message types from FDL

5. Dynamic Invocation (Reflection-based gRPC calls)
   └─> DynamicMessageService invokes commands/properties using generated types

Key Components

Component Description
DynamicConfigurator Main entry point; handles server discovery and provides access to services
ServerFeatureMap Dictionary mapping servers to their feature definitions (ConnectionInfo → Feature dictionary)
DynamicMessageService Service for invoking commands and properties dynamically
PayloadFactory Generates runtime protobuf message types from FDL metadata
Feature Parsed representation of a SiLA2 FDL XML definition

ServerFeatureMap Structure

IDictionary<ConnectionInfo, IDictionary<string, Feature>>
            ↓                      ↓           ↓
         Server Info         Feature FQI   Feature Definition

// Example access pattern:
var feature = client.ServerFeatureMap[connectionInfo]["org.silastandard/core/SiLAService/v1"];

Usage Examples

Discovering Servers

Automatic Discovery (Recommended)

// Discovers all SiLA2 servers on the network via mDNS
var serverFeatureMap = await client.GetFeatures();

Console.WriteLine($"Found {client.ServerFeatureMap.Count} servers:");
foreach (var server in client.ServerFeatureMap)
{
    Console.WriteLine($"\nServer: {server.Key.ServerName}");
    Console.WriteLine($"  Address: {server.Key.Address}:{server.Key.Port}");
    Console.WriteLine($"  UUID: {server.Key.ServerUuid}");
    Console.WriteLine($"  Features:");
    foreach (var feature in server.Value)
    {
        Console.WriteLine($"    - {feature.Key}");
    }
}

Manual Connection (Known Servers)

// Connect to specific servers when addresses are known
var connections = new[]
{
    new ConnectionInfo("192.168.1.100", 50052),
    new ConnectionInfo("192.168.1.101", 50052)
};

var serverFeatureMap = await client.GetFeatures(connections);

Calling Unobservable Properties

Unobservable properties return a single value immediately.

// Get a gRPC channel
var connectionInfo = client.ServerFeatureMap.Keys.First();
var channel = await client.GetChannel(connectionInfo.Address, connectionInfo.Port);

// Find the feature containing the property
var silaServiceFeature = client.ServerFeatureMap[connectionInfo]
    .Values.Single(x => x.FullyQualifiedIdentifier == "org.silastandard/core/SiLAService/v1");

// Call the property using dynamic typing
dynamic serverUUIDResponse = client.DynamicMessageService
    .GetUnobservableProperty("ServerUUID", channel, silaServiceFeature);

Console.WriteLine($"Server UUID: {serverUUIDResponse.ServerUUID.Value}");

// Alternative: Cast to specific protobuf type if known
dynamic serverNameResponse = client.DynamicMessageService
    .GetUnobservableProperty("ServerName", channel, silaServiceFeature);
Console.WriteLine($"Server Name: {serverNameResponse.ServerName.Value}");

Output:

Server UUID: 12345678-1234-1234-1234-123456789abc
Server Name: Temperature Controller

Calling Unobservable Commands

Unobservable commands execute immediately and return a result.

// Find the feature containing the command
var testFeature = client.ServerFeatureMap[connectionInfo]
    .Values.Single(x => x.FullyQualifiedIdentifier == "org.silastandard/test/UnobservableCommandTest/v1");

// Prepare parameters as a dictionary (parameter names must match FDL identifiers)
var payloadMap = new Dictionary<string, object>
{
    { "Integer", new Sila2.Org.Silastandard.Protobuf.Integer { Value = 42 } },
    { "String", new Sila2.Org.Silastandard.Protobuf.String { Value = "Hello" } }
};

// Execute the command
dynamic response = client.DynamicMessageService.ExecuteUnobservableCommand(
    "JoinIntegerAndString",
    channel,
    testFeature,
    payloadMap);

Console.WriteLine($"Joined Result: {response.JoinedParameters.Value}");

Output:

Joined Result: 42-Hello

Parameter Types: Use standard SiLA2 protobuf types from the Sila2.Org.Silastandard.Protobuf namespace:

  • Integer (int)
  • Real (double)
  • String (string)
  • Boolean (bool)
  • Binary (byte[])
  • Date / Time / Timestamp
  • Custom structures and lists (see Advanced Topics)

Subscribing to Observable Properties

Observable properties stream continuous value updates.

// Find the feature with the observable property
var observablePropertyFeature = client.ServerFeatureMap[connectionInfo]
    .Values.Single(x => x.FullyQualifiedIdentifier == "org.silastandard/test/ObservablePropertyTest/v1");

// Subscribe to the property stream
var propertyStream = client.DynamicMessageService.SubcribeObservableProperty(
    "Alternating",
    channel,
    observablePropertyFeature);

// Consume the stream using async enumeration
await foreach (var value in propertyStream)
{
    dynamic propertyValue = value;
    Console.WriteLine($"Property value: {propertyValue.Alternating.Value}");

    // You can break out of the loop to stop subscription
    if (someCondition) break;
}

Output:

Property value: True
Property value: False
Property value: True
Property value: False
...

Stream Control: The subscription remains active until:

  • You break out of the await foreach loop
  • The server stops streaming values
  • The gRPC channel is disposed

Executing Observable Commands

Observable commands are long-running operations that provide progress updates and eventual results.

// Find the feature with the observable command
var observableCommandFeature = client.ServerFeatureMap[connectionInfo]
    .Values.Single(x => x.FullyQualifiedIdentifier == "org.silastandard/test/ObservableCommandTest/v1");

// Prepare parameters
var payloadMap = new Dictionary<string, object>
{
    { "N", new Sila2.Org.Silastandard.Protobuf.Integer { Value = 10 } },
    { "Delay", new Sila2.Org.Silastandard.Protobuf.Real { Value = 1.0 } }
};

// Execute the command (returns immediately with a UUID and streams)
var result = client.DynamicMessageService.ExecuteObservableCommand(
    "Count",
    channel,
    observableCommandFeature,
    payloadMap);

var confirmation = result.Item1;           // CommandConfirmation (with UUID)
var executionInfoStream = result.Item2;    // ExecutionInfo stream (progress)
var intermediateStream = result.Item3;     // Intermediate responses (if defined)
var responseType = result.Item4;           // Type for final result retrieval

Console.WriteLine($"Command started with UUID: {confirmation.CommandExecutionUUID.Value}");

// Monitor execution progress
await foreach (var executionInfo in executionInfoStream)
{
    Console.WriteLine($"Status: {executionInfo.CommandStatus}");
    Console.WriteLine($"Progress: {executionInfo.ProgressInfo?.Value ?? 0:P0}");

    if (executionInfo.CommandStatus == ExecutionInfo.Types.CommandStatus.FinishedSuccessfully
        || executionInfo.CommandStatus == ExecutionInfo.Types.CommandStatus.FinishedWithError)
    {
        break;
    }
}

// Retrieve the final result
dynamic finalResult = client.DynamicMessageService.GetObservableCommandResult(
    confirmation.CommandExecutionUUID,
    "Count",
    channel,
    observableCommandFeature,
    responseType);

Console.WriteLine($"Final result: {finalResult.IterationResponse.Value}");

Output:

Command started with UUID: 87654321-4321-4321-4321-abcdef123456
Status: Running
Progress: 0%
Status: Running
Progress: 10%
...
Status: FinishedSuccessfully
Progress: 100%
Final result: 10

Intermediate Responses: Some observable commands define intermediate responses in their FDL for detailed progress information beyond ExecutionInfo. Access them via result.Item3 if not null.


API Reference

DynamicConfigurator

Constructor

public DynamicConfigurator(IConfiguration configuration, string[] args)

Initializes the configurator with application configuration and command-line arguments.

Properties

  • ServerFeatureMap: Dictionary mapping servers to their feature definitions
  • DynamicMessageService: Service for invoking commands and properties
  • PayloadFactory: Factory for creating dynamic protobuf types
  • DiscoveredServers: Dictionary of discovered servers (inherited from Configurator)

Methods

// Discover servers and retrieve feature definitions
Task<IDictionary<ConnectionInfo, IDictionary<string, Feature>>> GetFeatures(
    IEnumerable<ConnectionInfo> connections = null)

// Get a gRPC channel to a server
Task<GrpcChannel> GetChannel(string address, int port)

// Search for servers via mDNS (inherited from Configurator)
Task<IDictionary<Guid, ConnectionInfo>> SearchForServers(
    TimeSpan? searchDuration = null)

DynamicMessageService

Unobservable Property

object GetUnobservableProperty(
    string propertyName,
    GrpcChannel channel,
    Feature feature,
    Grpc.Core.Metadata metadata = null)

Observable Property

IAsyncEnumerable<object> SubcribeObservableProperty(
    string propertyName,
    GrpcChannel channel,
    Feature feature,
    Grpc.Core.Metadata metadata = null)

Unobservable Command

object ExecuteUnobservableCommand(
    string operationName,
    GrpcChannel channel,
    Feature feature,
    IDictionary<string, object> payloadMap = null,
    Grpc.Core.Metadata metadata = null)

Observable Command

Tuple<CommandConfirmation, IAsyncEnumerable<ExecutionInfo>, IAsyncEnumerable<object>, Type>
ExecuteObservableCommand(
    string operationName,
    GrpcChannel channel,
    Feature feature,
    IDictionary<string, object> payloadMap = null,
    Grpc.Core.Metadata metadata = null)

Observable Command Result

object GetObservableCommandResult(
    CommandExecutionUUID cmdId,
    string operationName,
    GrpcChannel channel,
    Feature feature,
    Type responseType,
    Grpc.Core.Metadata metadata = null)

PayloadFactory

// Generate property payload types
Tuple<Type, Type> GetPropertyPayloadTypes(Feature feature, string operation)

// Generate command payload types
Tuple<Type, Type> GetCommandPayloadTypes(Feature feature, string operation)

// Generate observable command payload types (with intermediate responses)
Tuple<Type, Type, Type> GetCommandPayloadTypesWithIntermediateCommandResponseType(
    Feature feature, string operation)

Configuration Reference

Complete appsettings.json Structure

{
  "DetailedErrors": true,
  "Logging": {
    "LogLevel": {
      "Default": "Debug",
      "System": "Information",
      "Grpc": "Debug",
      "Microsoft": "Information",
      "Microsoft.Hosting.Lifetime": "Warning"
    }
  },
  "Connection": {
    "FQHN": "localhost",
    "Port": 50052,
    "ServerDiscovery": {
      "NIC": "Ethernet",
      "ServiceName": "_sila._tcp"
    }
  }
}

Configuration Options Explained

Key Description Default Examples
Connection.FQHN Fallback host for manual connections localhost 192.168.1.100, temperature-server.local
Connection.Port Fallback port for manual connections 50052 50052 (standard SiLA2 port)
ServerDiscovery.NIC Network interface for mDNS discovery (default) "Ethernet", "eth0", "WLAN0", "" (empty = default)
ServerDiscovery.ServiceName mDNS service type suffix _sila._tcp _sila._tcp (standard)
Logging.LogLevel.Grpc gRPC diagnostic level Information Debug (verbose), Warning (minimal)

Network Interface Selection

Windows Examples:

  • "Ethernet" - Wired Ethernet adapter
  • "Wi-Fi" - Wireless adapter
  • "vEthernet (Default Switch)" - Hyper-V virtual adapter

Linux/macOS Examples:

  • "eth0" - Primary Ethernet
  • "wlan0" - Wireless interface
  • "en0" - macOS Ethernet/Wi-Fi

Leave empty ("") to use the system's default network interface.


Comparison: SiLA2.Client vs SiLA2.Client.Dynamic

Feature SiLA2.Client SiLA2.Client.Dynamic
Proto Files Required Yes No
Compile-Time Code Gen Yes (protoc) No
Type Safety Strong (compile-time) Dynamic (runtime)
IntelliSense Support Full Limited (dynamic typing)
Runtime Flexibility None Full
Feature Discovery Manual Automatic
Performance Fastest Slight overhead (reflection)
Use Case Production clients for known devices Generic tools, testing, discovery apps
Code Changes on Feature Update Rebuild required None
Complexity Higher initial setup Lower initial setup

When to Choose:

  • SiLA2.Client: Production clients with known feature sets, performance-critical applications
  • SiLA2.Client.Dynamic: Universal clients, testing tools, discovery applications, rapid prototyping

Advanced Topics

Working with Complex Data Types

Structures (Nested Messages)

// For commands that accept structures, build nested objects
var structure = new Dictionary<string, object>
{
    { "FieldName1", new Sila2.Org.Silastandard.Protobuf.String { Value = "Value" } },
    { "FieldName2", new Sila2.Org.Silastandard.Protobuf.Integer { Value = 123 } }
};

var payloadMap = new Dictionary<string, object>
{
    { "ParameterName", structure }
};

Lists (Repeated Fields)

// For commands that accept lists/arrays
var list = new List<Sila2.Org.Silastandard.Protobuf.Integer>
{
    new Sila2.Org.Silastandard.Protobuf.Integer { Value = 1 },
    new Sila2.Org.Silastandard.Protobuf.Integer { Value = 2 },
    new Sila2.Org.Silastandard.Protobuf.Integer { Value = 3 }
};

var payloadMap = new Dictionary<string, object>
{
    { "ValueList", list }
};

Constrained Data Types

SiLA2 supports constrained data types (e.g., Real with min/max, String with regex). The underlying protobuf types are the same; validation happens server-side:

// Even if the FDL defines constraints, use the base type
var payloadMap = new Dictionary<string, object>
{
    { "Temperature", new Sila2.Org.Silastandard.Protobuf.Real { Value = 25.5 } }
};
// Server will validate against constraints defined in FDL

Error Handling

Catching SiLA2 Errors

using Grpc.Core;

try
{
    dynamic response = client.DynamicMessageService.ExecuteUnobservableCommand(
        "SetTemperature", channel, feature, payloadMap);
}
catch (RpcException ex) when (ex.StatusCode == StatusCode.InvalidArgument)
{
    Console.WriteLine($"Invalid parameter: {ex.Status.Detail}");
}
catch (RpcException ex) when (ex.StatusCode == StatusCode.Unavailable)
{
    Console.WriteLine($"Server unavailable: {ex.Status.Detail}");
}
catch (RpcException ex)
{
    // Check for SiLA2 DefinedExecutionError in metadata
    var silaError = ex.Trailers.Get("sila2-error");
    if (silaError != null)
    {
        Console.WriteLine($"SiLA2 Error: {silaError.Value}");
    }
    else
    {
        Console.WriteLine($"gRPC Error: {ex.Message}");
    }
}

Checking Observable Command Errors

await foreach (var executionInfo in executionInfoStream)
{
    if (executionInfo.CommandStatus == ExecutionInfo.Types.CommandStatus.FinishedWithError)
    {
        Console.WriteLine($"Command failed!");
        Console.WriteLine($"Error: {executionInfo.Message?.Value ?? "Unknown error"}");
        break;
    }
}

Performance Considerations

Caching Considerations:

  • Feature definitions are retrieved once during GetFeatures() and stored in ServerFeatureMap
  • Generated protobuf types are cached by PayloadFactory to avoid redundant IL emission
  • Reusing gRPC channels improves performance (avoid creating new channels for each call)

Best Practices:

// Good: Reuse channel
var channel = await client.GetChannel(server.Address, server.Port);
for (int i = 0; i < 100; i++)
{
    var result = client.DynamicMessageService.GetUnobservableProperty(..., channel, ...);
}

// Bad: Creating new channel each time (overhead)
for (int i = 0; i < 100; i++)
{
    var channel = await client.GetChannel(server.Address, server.Port);
    var result = client.DynamicMessageService.GetUnobservableProperty(..., channel, ...);
}

Performance Impact:

  • First call to a command/property: ~5-10ms overhead (type generation)
  • Subsequent calls: ~1-2ms overhead (reflection-based invocation)
  • For high-frequency operations (>1000 calls/sec), consider standard SiLA2.Client

Thread Safety

Thread-Safe Components:

  • DynamicConfigurator - Safe for concurrent access
  • ServerFeatureMap - Read-only after population, safe for concurrent reads
  • DynamicMessageService - Stateless, safe for concurrent calls

Not Thread-Safe:

  • gRPC channels should not be disposed while in use
  • Observable property/command streams should be consumed sequentially
// Safe: Multiple threads calling different commands
await Task.WhenAll(
    Task.Run(() => client.DynamicMessageService.GetUnobservableProperty(...)),
    Task.Run(() => client.DynamicMessageService.ExecuteUnobservableCommand(...))
);

Examples and Resources

Integration Test Example

A complete working example is available in the repository:

Location: src/Tests/SiLA2.IntegrationTests.DynamicClientApp/

This example demonstrates:

  • Server discovery via mDNS
  • Feature enumeration and introspection
  • Calling unobservable properties and commands
  • Subscribing to observable properties
  • Executing and monitoring observable commands

Run the example:

# Start the test server
dotnet run --project src/Tests/SiLA2.IntegrationTests.ServerApp

# Run the dynamic client (in another terminal)
dotnet run --project src/Tests/SiLA2.IntegrationTests.DynamicClientApp

Additional Resources


Troubleshooting

mDNS Not Finding Servers

Symptoms: GetFeatures() returns empty ServerFeatureMap

Solutions:

  1. Check network interface:

    "ServerDiscovery": {
      "NIC": "Ethernet"  // Try different interface names
    }
    

    Use ipconfig (Windows) or ifconfig (Linux/macOS) to list interface names

  2. Verify server is announcing:

    • Ensure the SiLA2 server has called Start() to begin mDNS announcement
    • Check server logs for "mDNS announcement started" message
  3. Firewall/network issues:

    • mDNS uses UDP port 5353 - ensure it's not blocked
    • Check that client and server are on the same subnet
  4. Use manual connection:

    var serverFeatureMap = await client.GetFeatures(new[] {
        new ConnectionInfo("192.168.1.100", 50052)
    });
    

Certificate Errors

Symptoms: RpcException with StatusCode.Unavailable or SSL/TLS errors

Solutions:

  1. Development environments: Most SiLA2 servers use self-signed certificates

    // Note: DynamicConfigurator handles certificate validation automatically
    // For custom certificate handling, see the main repository documentation
    
  2. Accept self-signed certificates: The first connection to a server may prompt a certificate warning in browsers (for web-based servers)

Feature Not Found Errors

Symptoms: ArgumentException when calling commands/properties

Solutions:

  1. Verify feature identifier:

    // List all available features
    foreach (var feature in client.ServerFeatureMap[connectionInfo].Keys)
    {
        Console.WriteLine(feature);
    }
    
  2. Check command/property name:

    // Inspect feature definition
    var feature = client.ServerFeatureMap[connectionInfo]["org.example/feature/MyFeature/v1"];
    Console.WriteLine(feature); // Prints full FDL XML
    
  3. Case sensitivity: Command and property names are case-sensitive

gRPC Connection Timeouts

Symptoms: Operations hang or timeout

Solutions:

  1. Check server is running:

    netstat -an | grep 50052  # Linux/macOS
    netstat -an | findstr 50052  # Windows
    
  2. Increase timeout:

    // Configure channel options (if needed)
    var channel = GrpcChannel.ForAddress($"https://{address}:{port}", new GrpcChannelOptions
    {
        HttpHandler = new SocketsHttpHandler
        {
            PooledConnectionIdleTimeout = Timeout.InfiniteTimeSpan,
            KeepAlivePingDelay = TimeSpan.FromSeconds(60),
            KeepAlivePingTimeout = TimeSpan.FromSeconds(30)
        }
    });
    
  3. Check gRPC logs: Set "Logging.LogLevel.Grpc": "Debug" in appsettings.json

Debugging Tips

Enable verbose logging:

{
  "Logging": {
    "LogLevel": {
      "Default": "Debug",
      "Grpc": "Debug",
      "SiLA2": "Debug"
    }
  }
}

Inspect feature definitions:

// Print full FDL XML
var feature = client.ServerFeatureMap[connectionInfo]["feature.fqi"];
Console.WriteLine(feature.ToString());  // Shows XML structure

Test with official tools:

  • Use the SiLA Universal Client (Python) to verify server is working
  • Compare behavior with standard SiLA2.Client implementation

Get Help


Contributing

This project is open source under the MIT License. Contributions are welcome!

Repository: https://gitlab.com/SiLA2/sila_csharp

How to contribute:

  1. Fork the repository
  2. Create a feature branch
  3. Make your changes
  4. Submit a merge request

See the main repository README for development setup instructions.


License

MIT License

This software is provided under the MIT License. See the main repository for full license text.


Maintainer

Christoph Pohl (@Chamundi)

Vulnerability Policy

For security issues, please follow the SiLA2 Vulnerability Policy.

Product Compatible and additional computed target framework versions.
.NET 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. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
10.2.2 37 2/12/2026
10.2.1 92 1/25/2026
10.2.0 190 12/23/2025
10.1.0 138 11/29/2025
10.0.0 302 11/11/2025
9.0.4 226 6/25/2025
9.0.3 169 6/21/2025
9.0.2 212 1/6/2025
9.0.1 202 11/17/2024
9.0.0 184 11/13/2024
8.1.2 218 10/20/2024
8.1.1 220 8/31/2024
8.1.0 794 2/11/2024
8.0.0 274 11/15/2023
7.5.4 208 10/27/2023
7.5.3 278 7/19/2023
7.5.2 258 7/3/2023
7.5.1 272 6/2/2023