Skip to main content

Scripting

Introduction

ThisThe pageemDrive willConfigurator describescripting howfeature lets you create custom C# (.cs) scripts to usecommunicate with CAN Open devices. Scripts can be run in a dedicated Script Window within the application. You can perform actions such as reading and writing device parameters (SDO operations), sending direct commands, managing device states via NMT commands, and even logging or displaying messages. This guide explains the basics—from script file creation to using the scripting feature.API Tofor usesimple theoperations.

feature

User emDriveInterface ConfiguratorOverview

needs

When to have an open connection with an emDrive device. Scripts are written in C# (.cs files). Toyou open the Script Window navigate to the menu bar,(via Device > Script.), you will see several controls:

User
    interface

ScriptWindow.png

The

  • Browse Buttonbutton: will openOpens a File Open window toso chooseyou thecan select your C# script file. Once a file is loadedloaded, the fileits path will beis displayed in the textbox next to it.

    the button.
  • The Run Script Button: Starts script buttonexecution.

    will
  • start the execution of the script.

  • Stop Buttonwill: stopTerminates thea execution of therunning script.

  • TheInfinite ScriptLoop hasCheckbox: infiniteIndicates loopwhen checkboxyour isscript used for scripts withcontains long-running or infinite loops. ThisFor willscripts automaticallythat getuse a CancellationToken (introduced in version 2.9.0.0 and later), this option is set when the file is loaded if it contains the Cancellation Token implementation.automatically.

  • ScriptsShortcut Assignment: You can beadd addedscripts to Shortcutspredefined shortcuts by selecting theone desired shortcut infrom the Set script to shortcut button drop-down menu and clicking the Set button.

  • Script Editor: A simple editor is available from within the window to quickly modify and save your script.

  • Command Overview & Log Sections: These areas display feedback messages when API calls (such as SDO reads/writes or logging commands) are executed.

  • Long-running scripts cannot be used with Shortcuts.

    The window also comes with a simple script editor that can be opened with the Edit script.

    Below these controls is the Command Overview section which will be populated when certain API calls are made.

    To the right of it is the Log section which will print text if the Logging API calls are made.ScriptWindow.png

    Creating a scriptScript fileFile

    ScriptAll filesscripts aremust be written in C#. CreateYou acan .csuse file in your desiredany text editor or downloadan oneIDE oflike theVisual Studio Code to create a .cs file. Two common templates providedare in the attachments.provided:

    Basic Template

    EveryFor scriptquick needsscripts thewithout long-running loops, use a template similar to this:

    using System;
    using emDrive_Configurator;  // or "using eDrive_Configurator;" for versions 2.5.4.0 and usinglower
    
    emDrive_Configurator; using statements as well as a public class Script
    with{
        the public void Main()
        {
            // Your code here (e.g., SDO read/write, logging, etc.)
            Procedure.Log("Hello from the basic script!");
        }
    } method.

    Version 2.5.4.0 and lower use using eDrive_Configurator; instead of using emDrive_Configurator;.

    If

    Long-Running long-runningTemplate (Infinite or infiniteLooping loopsScripts)

    are

    For usedscripts the userthat must addrun usingcontinuously System.Threading;(e.g., andmonitoring sensor values), change the mainMain method to publicaccept voida Main(CancellationToken).CancellationToken. This lets the tool cancel your loop when you click Stop.

    This feature is available only from version 2.9.0.0 and onwards. Long-running scripts should not be used in versions below 2.9.0.0.

    Basic
    using template
    System; using System.Threading; using emDrive_Configurator; // or "using eDrive_Configurator;" for versions 2.5.4.0 and lower public class Script { public void Main(CancellationToken cancellationToken) { while (!cancellationToken.IsCancellationRequested) { // Periodically execute code (e.g., monitor sensor value) Procedure.Log("Script running..."); Procedure.Delay(1000); // Pause for one second } Procedure.Log("Script canceled."); } }

    Ensure you include using System.Threading; if you are using long-running loops with a cancellation token. (Version 2.9.0.0 and later support this feature.)

    Scripting API Methods

    The basicemDrive templateConfigurator exposes several API methods that you can use in your scripts. Below is useda detailed summary of the methods available for interacting with your CAN Open device.

    SDO Data Transfers

    SDO_Read Methods

    These methods read data (Service Data Objects) from the device. They come in several overloads:

    SDO_Read(int nodeId, uint index, ushort subindex, out object value, out string errMessage)

    Usage: Reads a parameter from a specified node and object index/subindex. The method converts the device’s hex response into a readable value and returns any error messages if the read fails.

    SDO_Read(int NodeId, uint Index, ushort Subindex)

    Usage: Returns the read value as a dynamic type after automatically processing the hex data.

    SDO_Read(int NodeId, uint Index, ushort Subindex, Action<CO_Object, dynamic> Parser)

    Usage: Reads data and passes the value to a provided callback (Parser) for further processing.

    SDO_Write Methods

    These methods write a value to a CAN Open object.

    SDO_Write(int nodeId, uint index, ushort subindex, dynamic value, out string errMessage)

    Usage: Writes the specified value to a node’s object. It logs the operation and returns a message indicating success or error.

    Direct Command Execution

    Direct_Command(string text)

    Usage: Sends a direct command (as a simple string) to the device and returns the response.

    Timing Control

    Delay(int Duration)

    Usage: A simple wrapper around Thread.Sleep that pauses script execution. The duration is in milliseconds.

    Network Management (NMT) Commands

    These methods allow you to change the state of CAN Open nodes:

    NMT_Operational() and NMT_Operational(int nodeId)

    Usage: Switch the device (or a specific node) to Operational Mode.

    NMT_Preoperational() and NMT_Preoperational(int nodeId)

    Usage: Set the device (or node) into Preoperational Mode.

    NMT_Stopped() and NMT_Stopped(int nodeId)

    Usage: Stops the device (or node).

    NMT_Reset() / NMT_Reset(int nodeId) and NMT_Reset_Communication() / NMT_Reset_Communication(int nodeId)

    Usage: Resets the device or its communication link.

    User Interaction and Logging

    ShowMessagebox(string text)

    Usage: Display a standard message box to the user.

    ShowDialog(string text)

    Usage: Open a dialog window that returns a Boolean value (for example, after the user confirms an action).

    InputDialog(string caption)

    Usage: Shows a prompt for user input and returns the entered text.

    ShowInConsole(string message) and Log(string Text)

    Usage: Log messages or display status information in the console or the dedicated logging area of the Script Window.

    Watch Interoperability

    To help with real-time monitoring, you can integrate with the Watch area in the user interface:

    TryAddScriptEntryToWatch(string name, int nodeId, uint index, ushort subindex, ushort datatype)

    Usage: Attempts to create a virtual object in Watch. If an object with the same nodeId/index/subindex already exists, the addition fails.

    TryAddScriptEntryValue(int nodeId, uint index, ushort subindex, dynamic value)

    Usage: Writes a value to an existing Watch entry.

    TryGetWatchValue(int nodeId, uint index, ushort subindex, out double value)

    Usage: Retrieves the last value shown in Watch for the specified parameters.

    WatchScriptObject Class

    This class wraps the Watch interoperability methods in a user-friendly way. It automatically assigns an index (default 0xFFFF) and increments the subindex for each new object. The available methods include:

    public class WatchScriptObject
    {
        // Properties
        public string Name { get; }
        public int NodeId { get; } // Default: 0
        public uint Index { get; } // Default: 0xFFFF
        public ushort SubIndex { get; }
        public ushort DataType { get; }
      
        // Constructors
        public WatchScriptObject(string name, ushort datatype);
        public WatchScriptObject(string name, ushort subindex, ushort datatype);
        public WatchScriptObject(string name, uint index, ushort subindex, ushort datatype);
    
        // Methods
        public bool TryToAddToWatch();
        public bool TryToAddValueToWatch(dynamic value);
    }

    TryToAddToWatch(): Adds the object to Watch.

    TryToAddValueToWatch(dynamic value): Updates the Watch entry with a new value.

    Script Examples

    Below are two example scripts that don'tillustrate containhow long-runningto loops.use the API methods to interact with CAN Open devices.

    Example 1: Basic SDO Read/Write

    This example shows how to read a value from a CAN Open object and then write a new temporary value.

    /*
      Author:       Amadej Arnus
      Date:         2024-05-08
    */
    
    using System;
    using emDrive_Configurator;
    
    public class Script
    {
        public void Main()
        {
            // YourDefine codenode goesand hereobject identifiers (using temporary indexes)
            int nodeId = 1;
            uint index = 0x2000;
            ushort subindex = 0x01;
            
            object readValue;
            string errMessage;
            
            // Attempt to read the current value from the device
            if (Procedure.SDO_Read(nodeId, index, subindex, out readValue, out errMessage))
            {
                Procedure.Log("SDO Read successful. Value: " + readValue);
            }
            else
            {
                Procedure.Log("SDO Read error: " + errMessage);
            }
            
            // Set a temporary value and write it to the device
            dynamic tempValue = 123;  // Change 123 to any temporary test value as needed
            if (Procedure.SDO_Write(nodeId, index, subindex, tempValue, out errMessage))
            {
                Procedure.Log("SDO Write successful. New Value: " + tempValue);
            }
            else
            {
                Procedure.Log("SDO Write error: " + errMessage);
            }
        }
    }

    Example 2: Break Check Test with Long-runningRunning template

    Loop

    This templatescript continuously reads from the device and exits the loop if a defined break condition is usedmet. forIt scripts that are expected to contain long-running or infinite loops. This allowsdemonstrates the useruse toof clicka StopCancellationToken inso the Script Window which will cancel the cancellation token and exitthat the script execution.can Thebe userstopped mustvia checkthe for CancellationToken.IsCancellationRequested value in their loops for this to work.UI.

    /*
      Author:       Amadej Arnus
      Date:         2024-05-98
    */
    
    using System;
    using emDrive_Configurator;System.Threading;
    using System.Threading;emDrive_Configurator;
    
    public class Script
    {
        // Use this template for scripts with long-running loops.
        public void Main(CancellationToken cancellationToken)
        {
            //int YournodeId code= goes1;
            hereuint //index Example= of0x2000;
            loopushort withsubindex cancellation= token0x01;
            
            while (true)
            {
                // Your code goes here
    
                if (!cancellationToken.IsCancellationRequested)
            {
                dynamic? value;
                string errMessage;
                
                if (Procedure.SDO_Read(nodeId, index, subindex, out value, out errMessage))
                {
                    Procedure.Log("Current SDO Value: " + value);
                }
                else
                {
                    Procedure.Log("Read error: " + errMessage);
                }
                
                // Check if a break condition is met (example: value equals a specific test value)
                if (value != null && value == condition)
                {
                    Procedure.Log("Break condition met. Exiting loop.");
                    break;
                }
                
                // Wait for 1 second before next iteration
                Procedure.Delay(1000);
            }
            
            Procedure.Log("Script execution completed.");
        }
    }

    Advanced Scripting API

    The following section will list and describe all of the available API calls.

    Script - Script-Watch Interoperability

    This functionality is only available for emDrive Configurator 2.13.0.0 and later.

    These API calls allow the user to add virtual script objects to Watch and write to them, as well as request the last value read for a specific object already in Watch. This can be achieved by using the WatchScriptObject class or the method calls described below.

    WatchScriptObject class

    The WatchScriptObject class uses the API calls described below but is a more user-friendly approach to this functionality. Unless specified the index and subindex properties will be set automatically. index will always be 0xFFFF and subindex will increment from 0 onwards. If the user wishes to reset the subindex counter they must call the ResetSubIndexCounter method.

    The TryToAddToWatch method will attempt to add the virtual object to Watch. See TryAddScriptEntryToWatch method for possible errors.

    The TryToAddValueToWatch method will attempt to add the value to the already created virtual object in Watch. See TryAddScriptEntryValue method for possible errors.

    public class WatchScriptObject
    {
        // Properties
        public string Name { get; }
        public int NodeId { get; } // Default: 0
        public uint Index { get; } // Default: 0xFFFF
        public ushort SubIndex { get; }
        public ushort DataType { get; }
      
        // Constructors
        public WatchScriptObject(string name, ushort datatype);
        public WatchScriptObject(string name, ushort subindex, ushort datatype);
        public WatchScriptObject(string name, uint index, ushort subindex, ushort datatype);
    
        // Methods
        public bool TryToAddToWatch();
        public bool TryToAddValueToWatch(dynamic value);
        public static void ResetSubIndexCounter();
    }

    TryAddScriptEntryToWatch method

    Attempts to create a new script virtual object in Watch with the specified name, nodeId, index, subindex, and datatype. Returns true if successful or false if failed.

    public static bool TryAddScriptEntryToWatch(string name, int nodeId, uint index, ushort subindex, ushort datatype)

    Fail reasons:

    • Object with specified nodeId, index and subindex already exists in Watch.

    TryAddScriptEntryValue method

    Attempts to write value to the script virtual object in Watch. Returns true if successful or false if failed.

    public static bool TryAddScriptEntryValue(int nodeId, uint index, ushort subindex, dynamic value)

    Fail reasons:

    • Object with specified nodeId, index and subindex does not exist in Watch.
    • Failed to convert value to the declared datatype.

    TryGetLastWatchValue method

    Attempts to get the last read value of the object specified with nodeId, index, subindex and saves it to value. Returns true if successful or false if failed.

    public static bool TryGetWatchValue(int nodeId, uint index, ushort subindex, out double value)

    Fail reasons:

    • Object with specified nodeId, index and subindex does not exist in Watch.
    • Failed to convert object value to type double.

    Example file

    Example file for Script - Watch Interoperability (also available as download)
    /*
      Author:       Amadej Arnus
      Date:         2024-04-23
    */
    
    using System;
    using System.Windows.Forms;
    using emDrive_Configurator;
    using System.Threading;
    using Timer = System.Threading.Timer;
    
    using System.Collections.Generic;
    using System.Diagnostics;
    using System.Drawing;
    using System.Globalization;
    using System.IO;
    using System.IO.Ports;
    using System.Linq;
    using System.Management;
    using System.Text;
    using System.Text.RegularExpressions;
    
    public class Script
    {
        // Prepare error string
        string err = string.Empty;
    
        // Create object that we will attempt to read from watch
        CO_Object HwMonitorUdc = new CO_Object(0x3071, 0x00);
    
        public void Main(CancellationToken cancellationToken)
        {
            // This will run the example for the WatchScriptObject
            ObjectBasedVersion(cancellationToken);
    
            // This will run the example for the API calls
            //ObjectBasedVersion(cancellationToken);
        }
    
        private void ObjectBasedVersion(CancellationToken cancellationToken)
        {
            // Create new objects for watch-script interoperability
            Procedure.WatchScriptObject intObject = new Procedure.WatchScriptObject("ScriptObject_Int32", 0x0004);
            Procedure.WatchScriptObject floatObject = new Procedure.WatchScriptObject("ScriptObject_Real32", 0x0008);
    
            // Add int object to watch
            if (intObject.TryToAddToWatch())
            {
                LogResult("ScriptObject_Int32 added to watch");
            }
            else
            {
                LogResult("ScriptObject_Int32 not added to watch");
            }
    
            // Add float object to watch
            if (floatObject.TryToAddToWatch())
            {
                LogResult("ScriptObject_Real32 added to watch");
            }
            else
            {
                LogResult("ScriptObject_Real32 not added to watch");
            }
    
            int i = 0;
    
            // Do the loop until cancellationToken is requested
            while (!cancellationToken.IsCancellationRequested)
            {
                double hwMonitorValue = 0;
    
                // Read last value of HwMonitorUdc object
                if (HwMonitorUdc.TryGetWatchValue(1, out hwMonitorValue))
                {
                    i++;
    
                    // Set new value to int and float objects
                    if (!intObject.TryToAddValueToWatch(i))
                    {
                        LogResult("Failed to write value to ScriptObject_Int32");
                    }
    
                    if (!floatObject.TryToAddValueToWatch(hwMonitorValue * 1.5))
                    {
                        LogResult("Failed to write value to ScriptObject_Real32");
                    }
                }
                // If not successful, log the error
                else
                {
                    LogResult("Failed to get HwMonitorUdc value");
                }
    
                Procedure.Delay(200);
            }
        }
    
        private void ApiBasedVersion(CancellationToken cancellationToken)
        {
            // Add int object to watch
            if (TryAddScriptEntryToWatch("ScriptObject_Int32", 1, 0xFFFF, 0x01, datatype: 0x0004))
            {
                LogResult("ScriptObject_Int32 added to watch");
            }
            else
            {
                LogResult("ScriptObject_Int32 not added to watch");
            }
    
            // Add float object to watch
            if (TryAddScriptEntryToWatch("ScriptObject_Real32", 1, 0xFFFF, 0x02, datatype: 0x0008))
            {
                LogResult("ScriptObject_Real32 added to watch");
            }
            else
            {
                LogResult("ScriptObject_Real32 not added to watch");
            }
    
            int i = 0;
    
            // Do the loop until cancellationToken is requested
            while (!cancellationToken.IsCancellationRequested)
            {
                double hwMinutorValue = 0;
    
                // Read last value of HwMonitorUdc object
                if (TryGetWatchValue(1, 0x3071, 0x00, out hwMinutorValue))
                {
                    i++;
    
                    // Set new value to int and float objects
                    if (!TryAddScriptEntryValue(1, 0xFFFF, 0x01, i))
                    {
                        LogResult("Failed to write value to ScriptObject_Int32");
                    }
    
                    if (!TryAddScriptEntryValue(1, 0xFFFF, 0x02, hwMinutorValue * 1.5))
                    {
                        LogResult("Failed to write value to ScriptObject_Real32");
                    }
                }
                // If not successful, log the error
                else
                {
                    LogResult("Failed to get HwMonitorUdc value");
                }
    
                Procedure.Delay(200);
            }
        }
    
        // The following methods are just wrappers to shorten the method calls
        bool TryAddScriptEntryToWatch(string name, int nodeId, uint index, ushort subindex, ushort datatype)
        {
            return Procedure.TryAddScriptEntryToWatch(name, nodeId, index, subindex, datatype);
        }
        bool TryAddScriptEntryValue(int nodeId, uint index, ushort subindex, dynamic value)
        {
            return Procedure.TryAddScriptEntryValue(nodeId, index, subindex, value);
        }
        bool TryGetWatchValue(int nodeId, uint index, ushort subindex, out double value)
        {
            return Procedure.TryGetWatchValue(nodeId, index, subindex, out value);
        }
    
        void LogResult(string LogString)
        {
            LogString = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss") + ": " + LogString;
            Procedure.Log(LogString);
        }
    }
    
    // CO_Object for readability
    class CO_Object
    {
        public uint index;
        public ushort subindex;
        public dynamic value;
    
        public CO_Object(uint index, ushort subindex)
        {
            this.index = index;
            this.subindex = subindex;
        }
    
        public bool TryGetWatchValue(int nodeId, out double value)
        {
            return Procedure.TryGetWatchValue(nodeId, index, subindex, out value);
        }
    }
    

    Script Features

    Script features are extensions to the scripting functionality.

    In order to use script features, the user must add 

    using emDrive_Configurator.Scripts.Features;
    at the start of the script file.

    Modbus TCP

    This functionality is only available for emDrive Configurator 2.13.1.0 and later.

    Modbus_TCP.png

    ModbusTCP currently supports connection with Modbus Servers (Client Mode) through TCP. In order to connect to the device, you need to provide the IP Address, Port and Modbus Server ID (Bus Address).

    In the following Example, the IP is 127.0.0.1, the Port is 502 and the Modbus Server ID is 1:

    ModbusTCP ModbusDevice1 = new ModbusTCP("127.0.0.1", 502, 1);

    Alternatively, the device endianness can also be specified in the connection constructor:

    ModbusTCP ModbusLocalhost = new ModbusTCP("127.0.0.1", 502, 1, ModbusEndianness.LittleEndian);

    Default Endianness is set to Big Endian.

    Currently Read and Write Holding registers operation is supported using generic functions:

    short i16_var = 0;
    
    // Read Holding Register at Address 100
    if(ModbusLocalhost.ReadHoldingRegister<short>(100, out i16_var) == true)
    {
      // Read Successful
    }
    else
    {
      // Read Failed
    }
    
    // Write Holding Register at Address 100
    ModbusLocalhost.WriteHoldingRegister(100, i16_var);    

    Supported data types:
    • short
    • ushort
    • int
    • uint
    • float

    Useful Learning Resources

    If you’re new to C# or need additional reading to get familiar with the language and its concepts, check out these established sources:

    Additional Tips and Conclusion

    Version Considerations

    For scripts with long-running loops, ensure you are using version 2.9.0.0 or later, which supports using a CancellationToken.

    Earlier versions (e.g., 2.5.4.0 and lower) require a different namespace (using eDrive_Configurator;).

    Script Debugging

    Utilize the Log output function (Procedure.Log) to print messages and debug your script during execution.

    Integration with Watch

    Use the WatchScriptObject class to monitor variable values during script execution. This can help you verify that the SDO read/write operations are working as expected.

    By following this guide, even users with minimal technical expertise will be able to write, run, and troubleshoot simple C# scripts to interact with CAN Open devices via the emDrive Configurator. Experiment with the examples provided and consult the useful links if you need further insight into C# programming.

    Happy scripting!