Click or drag to resize

4.1.4 Card Configuration Tutorial

The Card Configuration tutorial shows a sample use case in which the card configuration is changed solely using the PDA API.

Three configuration cases are implemented and their pitfalls are described in comments:

  • Adding a built-in signal to the card's configuration

  • Adding a virtual signal calculated from built-in signals to the PDA's configuration

  • Adding a virtual signal calculated from an existing virtual signal and a built-in signal to the PDA's configuration

Complete Source Code
Card Configuration example
//-----------------------------------------------------------------------------
// This example demonstrates how to configure a card using the PDA API.
//-----------------------------------------------------------------------------

using RAYLASE.PDA.API;
using System.Diagnostics;
using System.Net;

public class Program
{
    public static async Task<int> Main( string[] args )
    {
        Console.WriteLine( "\n\nCard Configuration sample\n------------------\n\n" );

        if ( args.Length < 1 )
        {
            Console.WriteLine( "This tutorial requires a valid card IP address as argument" );
            return -1;
        }

        var cardIP = args[0];
        if ( !IPAddress.TryParse( cardIP, out _ ) )
        {
            Console.WriteLine( "The supplied 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[1];

        // 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
        {
            Config = new TraceDataSignalConfig
            {
                // 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
        {
            Config = new TraceDataSignalConfig
            {
                // 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
        {
            Config = new TraceDataSignalConfig
            {
                // 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.Config.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.Config.Signal.VirtualSignal is not null && x.Config.Signal.VirtualSignal.Key == gateEnabledVelocity.Config.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.Config.Signal.VirtualSignal is not null && x.Config.Signal.VirtualSignal.Key == velocity2D.Config.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;
    }
}