4.1.6 Card Switch Standalone Tutorial |
The C:\Program Files\RAYLASE\ProcessDataAnalyzer\API\SampleCode\Tutorials\CardSwitchStandaloneTutorial demonstrates how to programmatically configure the Process Data Analyzer for multi-card setups using the Standalone API. This tutorial focuses on adding both built-in and virtual signals to card configurations, including advanced features like signal dependencies and custom calculations.
This tutorial is particularly useful for:
Understanding programmatic configuration of multiple control cards
Learning how to create and configure virtual signals via the API
Managing signal dependencies in complex configurations
Working with the Standalone API for automated setup workflows
The CardSwitchStandaloneTutorial demonstrates the following key concepts:
Starting the Process Data Analyzer service using the Standalone API
Retrieving and modifying the PDA configuration programmatically
Adding built-in signals (physical signals from the control card)
Creating virtual signals with mathematical calculations
Building signal dependency chains (virtual signals based on other virtual signals)
Managing signal keys and ordering
Saving configurations back to the service
To run this tutorial, you need:
Two SP-ICE-3 control cards with valid IP addresses (or one card for basic testing)
ProcessDataAnalyzer installed with the Standalone API libraries
Visual Studio or .NET 8 SDK for building and running the tutorial
Basic understanding of C# and the Process Data Analyzer signal system
The tutorial demonstrates three approaches to configuration:
Direct API Configuration: Modify configuration objects in code and upload them to the service (demonstrated in this tutorial)
File-Based Configuration: Save configuration to JSON, edit manually, and reload (code included but commented out)
GUI Configuration: Use the PDA GUI to configure, which persists to the service configuration automatically
The tutorial focuses on direct API configuration for maximum flexibility and automation.
Built-in signals represent physical signals from the control card. The tutorial demonstrates adding the FPS (frames per second) signal:
var fpsSignal = new SignalConfig { Config = new TraceDataSignalConfig { // The signal type from the BuiltInSignal enum Signal = new Signal { BuiltInSignal = BuiltInSignal.BisFps }, // Enable the signal to be recorded IsEnabled = true, // Optional: Scale, offset, and timing can be configured //ScaleOffset = new ScaleOffset { Scale = 2, Offset = 3 }, //Timing = new SignalTiming //{ // MeasurementDelay = 9, // Offset = 8, // TrackingError = 6, // TransferDelay = 5 //} } }; // Add to card configuration firstConnectedCard.Signals.Add( fpsSignal );
Virtual signals are calculated from other signals using mathematical operations. The tutorial creates a 2D velocity signal from commanded X and Y positions:
var velocity2D = new SignalConfig { Config = new TraceDataSignalConfig { // Assign a unique key for this virtual signal Signal = new Signal { VirtualSignal = new VirtualSignal { Key = "Virtual0" } }, // Display name CustomName = "XY Velocity", // Unit for display and export Unit = "m/s", // Configure the calculation VirtualSignalConfig = new VirtualSignalConfig { Calculation = CalculationType.CtVelocity2D, // Input signals (order matters!) InputSignals = { new Signal { BuiltInSignal = BuiltInSignal.BisField0TxX }, new Signal { BuiltInSignal = BuiltInSignal.BisField0TxY } } }, IsEnabled = true } };
Available calculation types include velocity, multiplication, division, addition, subtraction, and more. Refer to the CalculationType enum for a complete list.
Virtual signals can depend on other virtual signals, creating calculation chains. The tutorial demonstrates creating a "gate-enabled velocity" that multiplies the velocity by the gate signal:
var gateEnabledVelocity = new SignalConfig { Config = new TraceDataSignalConfig { // Use a higher key number to ensure proper ordering Signal = new Signal { VirtualSignal = new VirtualSignal { Key = "Virtual1" } }, CustomName = "Gate ON Velocity", Unit = "m/s", VirtualSignalConfig = new VirtualSignalConfig { Calculation = CalculationType.CtMultiplication, InputSignals = { // Reference the previously created virtual signal new Signal { VirtualSignal = velocity2D.Config.Signal.VirtualSignal }, // Combine with a built-in signal new Signal { BuiltInSignal = BuiltInSignal.BisGate } } }, IsEnabled = true } };
When creating signal dependencies, ensure that:
|
The tutorial demonstrates two approaches to virtual signal key management:
1. Manual Key Assignment (Recommended):
Signal = new Signal { VirtualSignal = new VirtualSignal { Key = "Virtual0" } }
This approach gives you full control over signal ordering and dependencies.
2. Automatic Key Assignment:
Signal = new Signal { VirtualSignal = new VirtualSignal { Key = "XY Velocity" } }
Using a descriptive name as the key will auto-generate a unique key, but you must ensure dependencies are added in the correct order.
The tutorial requires two card IP addresses and demonstrates how to work with multiple cards:
// Get current configuration var config = await api.Configuration.GetPdaConfigAsync(); var cardConfigs = config.CardConfigurations; // Access specific card configurations var firstConnectedCard = cardConfigs[0]; var secondConnectedCard = cardConfigs[1]; // Add signals to specific cards firstConnectedCard.Signals.Add( velocity2D ); firstConnectedCard.Signals.Add( gateEnabledVelocity ); // Save updated configuration back to service await api.Configuration.SetPdaConfigAsync( cardConfigs );
Each card can have its own signal configuration, allowing for different setups across multiple scan heads or processing stations.
To run the CardSwitchStandaloneTutorial:
Build the tutorial project in Visual Studio or using dotnet build
Run the executable with two card IP addresses as arguments:
CardSwitchStandaloneTutorial.exe 10.2.0.101 10.2.0.102
Optional: Provide a custom path to ProcessDataAnalyzer.Service.exe as a third argument:
CardSwitchStandaloneTutorial.exe 10.2.0.101 10.2.0.102 "C:\CustomPath\ProcessDataAnalyzer.Service.exe"
The tutorial will configure the service and exit
View the results by starting the PDA GUI or using the Standalone API Tutorial to acquire data
The complete source code demonstrates all concepts in a working example:
//----------------------------------------------------------------------------- // This example demonstrates how to configure two cards and switching the // recording configuration of a card using the PDA API. //----------------------------------------------------------------------------- using RAYLASE.PDA.API; using System.Net; public class Program { public static async Task<int> Main( string[] args ) { Console.WriteLine( "\n\nCard Switch Standalone sample\n------------------\n\n" ); if ( args.Length < 2 ) { Console.WriteLine( "This tutorial requires two valid card IP addresses as argument" ); return -1; } var cardIP1 = args[0]; if ( !IPAddress.TryParse( cardIP1, out _ ) ) { Console.WriteLine( "The supplied first IP address argument is not a valid IP address" ); return -1; } var cardIP2 = args[1]; if ( !IPAddress.TryParse( cardIP2, out _ ) ) { Console.WriteLine( "The supplied second IP address argument is not a valid IP address" ); return -1; } // Default installation path of the ProcessDataAnalyzer.Service.exe var defaultServiceExePath = @$"{Environment.ExpandEnvironmentVariables( "%ProgramFiles%" )}\RAYLASE\ProcessDataAnalyzer\bin\ProcessDataAnalyzer.Service.exe"; // Check if a different path was specified in the arguments. var serviceExePath = args.Length <= 1 ? defaultServiceExePath : args[2]; // Get the API which auto-starts the PDA Server and is valid for this (Main()) scope. using var api = new RAYLASE.PDA.ClientLib.Api( serviceExePath ); // Get the current service configurations var config = await api.Configuration.GetPdaConfigAsync(); var cardConfigs = config.CardConfigurations; // The config can be saved to a service relative path as well and be // inspected/adapted manually and re-uploaded to the service // await api.Configuration.SavePdaConfigAsync( ".\servicePdaConfig.json" ); // Console.WriteLine( "Edit the servicePdaConfig.json, save and press any button to continue." ); // Console.Read(); // await api.Configuration.SetPdaConfig( ".\servicePdaConfig.json" ); // Alternatively, the configuration can be done in the PDA GUI and is reloaded on a service restart. // This example will focus on changing the configuration programmatically. //----------------------------------------------------------------------------- // Create a built-in and a virtual signal. //----------------------------------------------------------------------------- // First, let's create a physical and a virtual signal we want to record. // BuiltInSignals are physical signals on the card, VirtualSignals // represent a new signal created by the PDA and are defined through a mathematical // equation. var fpsSignal = new SignalConfig { // The new signal type. Signal = new Signal { BuiltInSignal = BuiltInSignal.BisFps }, // Enable the signal to be recorded and not only configured IsEnabled = true, // Scale offset and timing could be set as well. //ScaleOffset = new ScaleOffset { Scale = 2, Offset = 3 }, //Timing = new SignalTiming //{ // MeasurementDelay = 9, // Offset = 8, // PropagationDelay = 7, // TrackingError = 6, // TransferDelay = 5 //} }; var velocity2D = new SignalConfig { // It is also possible to set the name directly to the signal key in which case // the key is copied to the CustomName property and a valid key is auto assigned: // Signal = new Signal { VirtualSignal = new VirtualSignal { Key = "XY Velocity" } }, // This has the drawback that the order of virtual signals is determined by the order // in which the signals are added to the configuration below. We manually assign // the ID Virtual0 to this signal. Signal = new Signal { VirtualSignal = new VirtualSignal { Key = "Virtual0" } }, // Add a name to the new virtual signal. Multiple virtual signals can be // created using the same calculation type and thus only differ in their name. CustomName = "XY Velocity", // Display and export unit name Unit = "m/s", // Interpolate values //IsUserInterpolating = true, // Configure the virtual signal to take two input signals and compute the velocity. // We use commanded X and Y here since they are also available if no scanhead // is connected to the card. VirtualSignalConfig = new VirtualSignalConfig { Calculation = CalculationType.CtVelocity2D, // For calculations, the order of input signals is from left/first to right/last. // Dividing x by y would need x as first input signal and y as second. // If this calculation would be a division rather than velocity, we would be // dividing Field0TxY by Field0TxX. InputSignals = { // Filed 0: First field // Tx: Commanded signal (Rx would be received, measured) // X: Signal in X direction new Signal { BuiltInSignal = BuiltInSignal.BisField0TxX }, new Signal { BuiltInSignal = BuiltInSignal.BisField0TxY } } }, IsEnabled = true }; // It is possible to combine built-in and/or virtual signals in new virtual signals. // For demonstration purpose, let's multiply above velocity by the gate signal, which // will result in a velocity which will only be "active" during gate ON (otherwise the // velocity is multiplied by 0). var gateEnabledVelocity = new SignalConfig { // Make sure that this signal's ID is higher than the virtual input signal's ID. Signal = new Signal { VirtualSignal = new VirtualSignal { Key = "Virtual1" } }, CustomName = "Gate ON Velocity", Unit = "m/s", // Configure the new virtual signal. VirtualSignalConfig = new VirtualSignalConfig { Calculation = CalculationType.CtMultiplication, InputSignals = { new Signal {VirtualSignal = velocity2D.Signal.VirtualSignal }, new Signal { BuiltInSignal = BuiltInSignal.BisGate } } }, IsEnabled = true }; //----------------------------------------------------------------------------- // Add the new signals to the configuration //----------------------------------------------------------------------------- // The PDA configuration can be done for multiple devices at the same time so // a device ID must be specified to access the device specific configurations. // The device ID corresponds to the order in which the devices are connected to // the service. We have no device connected yet, but want to edit the first device // configuration since it will be created by default. var firstConnectedCard = cardConfigs[0]; firstConnectedCard.Signals.Add( fpsSignal ); // Make sure that the virtual signal key is not used already. If you want to change the signal, adopt the existing entry from the config. if ( !firstConnectedCard.Signals.Any( x => x.Signal.VirtualSignal is not null && x.Signal.VirtualSignal.Key == gateEnabledVelocity.Signal.VirtualSignal.Key ) ) { // Since we set the signal keys to Virtual1 for gateEnabledVelocity and to Virtual0 for velocity2D, // it is ok now to switch the order in which the signals are added to the card configuration. // If the signal key would be a name, make sure that virtual signal dependencies are added to the // config first. firstConnectedCard.Signals.Add( gateEnabledVelocity ); } if ( !firstConnectedCard.Signals.Any( x => x.Signal.VirtualSignal is not null && x.Signal.VirtualSignal.Key == velocity2D.Signal.VirtualSignal.Key ) ) { firstConnectedCard.Signals.Add( velocity2D ); } // Save the new configuration to the service // The configuration is shared between the GUI and the Service/Server and saved under // %ProgramData%\RAYLASE\ProcessDataAnalyzer\Configurations.json await api.Configuration.SetPdaConfigAsync( cardConfigs ); // Now we connect a card to the service, this could be done before configuring as well. // Starting the acquisition will get the traces of all configured built-in signals // and the PDA will process all virtual signals. // See the PDA GUI or other tutorials for acquiring and exporting data. return 0; } }
After completing this tutorial, you can:
Combine this configuration approach with acquisition using the Standalone API Tutorial
Explore trigger configuration with the Trigger Configuration Tutorial
Learn about multi-card support in the Multi Card Support topic
Create custom calculation types for specialized signal processing