Persistent configuration

This guide describes how to create a Persistent configuration binary file that can be loaded into a PMP (simulated) EtherCAT SubDevice.

During this guide you will learn how to:

  • Create an XML file as input for a persistent configuration updatable binary.

  • Upload the updatable binary to a PMP (simulated) EtherCAT SubDevice.

  • Activate the persistent configuration on the PMP (simulated) EtherCAT SubDevice.

Note

Currently, only PMP EtherCAT SubDevices support persistent configuration.

Introduction

PMP motion controllers and EtherCAT SubDevices do not store configuration parameters persistently by default. This means that all parameters are reset to their default value after every reboot. When using a PMP motion controller, the configuration for the motion controller and all devices in the EtherCAT chain is normally loaded via configuration XML file after startup. However, when using third-party masters it is often expected that EtherCAT SubDevices store their configuration parameters persistently. EtherCAT SubDevices with a PersistentConfiguration updatable (i.e. FoE file) can store a binary that specifies startup values for a list of EtherCAT objects.

Flow

The persistent configuration can be defined in a configuration XML file with format as described in Persistent configuration. A PmpConfigToPersistentConfig tool is included in the PMP generic installer (Miscellaneous/Persistent Configuration) to convert the XML configuration to binary format. Alternatively, the configuration can also directly be defined in a binary format. This format is specified in Persistent configuration.

Block diagram showing general usage of the PmpConfigToPersistentConfig tool. shows the workflow from configuration XML file to activated persistent configuration.

Block diagram showing general usage of the tool

Block diagram showing general usage of the PmpConfigToPersistentConfig tool.

Each of the steps is described in more detail below.

Prepare PMP configuration XML file

It is recommended to first create and validate a PMP configuration XML file by loading it with a PMP motion controller. If there are any errors while loading the file, resolve them before proceeding with the next steps.

Note

Objects can be non-persistable, these objects can not be set via the persistent configuration.

PMP configuration to persistent configuration converter

The PmpConfigToPersistentConfig tool converts PMP configuration XML files to a persistent configuration binary. The purpose of this tool is to generate this persistent configuration binary from a PMP configuration XML file.

Note

Limitations states the limitations of the conversion tool.

Applying the persistent configuration to the EtherCAT SubDevice

When using a PMP motion controller, upload the persistent configuration binary to PersistentConfiguration/Configuration updatable of the target EtherCAT slave. When using a third-party EtherCAT master, upload the persistent configuration binary to the PersistentConfiguration file using FoE. The product-specific documentation provides more details about the PersistentConfiguration file.

During the FoE upload the integrity and validity of the persistent configuration is validated using the checks listed in Persistent configuration validity checks and errors. Additional error details are reported through an asynchronous exception. If valid, the configuration is stored persistent, overwriting a previous configuration if present. If invalid, The FoE transfer is aborted with the applicable error code.

Note

An invalid persistent configuration upload removes a previous configuration from EtherCAT SubDevice memory.

After uploading the binary, reboot the EtherCAT SubDevice to apply the configuration.

Persistent configuration validity checks and errors

Error code

Description

Illegal (0x8004)

Firmware was not downloaded since last reset

FileHeaderNotExist (0x8010)

File contains a valid header

FirmwareDoesNotFitHardware (0x800D)

File size does not exceed limit

ChecksumError (0x800C)

File has valid data integrity and checksum

FirmwareDoesNotFitHardware (0x800D)

Referenced objects are present

FirmwareDoesNotFitHardware (0x800D)

Referenced objects are writable

FirmwareDoesNotFitHardware (0x800D)

Referenced objects are persistable

FirmwareDoesNotFitHardware (0x800D)

Objects are referenced with correct datatype

In case there are non-persistable objects, those are specified in the product-specific documentation.

The persistent configuration is cleared by uploading a zero size persistent configuration file or by uploading operational firmware.

See also

Persistent configuration

More details about the persistent configuration XML format.

See also

Persistent configuration

More details about the persistent configuration binary format.

Examples

The following paragraphs explain each example in detail.

Prerequisites

Before continuing with the guide, please make sure that the following prerequisites are met:

  • PMP should be installed with the API and Tooling features. Follow the Installation quick-start guide if this is not yet the case. A Typical installation includes the required API and Tooling features.

  • The persistent configuration guide project files should be available. These files can be downloaded from Downloads.

  • A hardware setup with a PC, Arcas, Cygnus D3. The Arcas and Cygnus D3 are used as reference throughout this example.

Verify PMP configuration XML file

The following XML configuration is used to create a persistent configuration binary:

1
2
3
4
5
6
7
<Controller Name="Arcas 5EG-1">
  <Controller Name="Cygnus D3-400-4-2">
    <AxisControl Name="AxisControl1">
      <Signal Name="MotorType">1</Signal>
    </AxisControl>
  </Controller>
</Controller>

Apply the configuration file to the controller to verify it is a valid configuration:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
static int Main()
{
    Pmp.ITopController topController = null;

    try
    {
        // Create system controller
        var address = "255.255.255.255";
        var system = new Pmp.System(address);

        var topControllerName = "Arcas 5EG-1";
        while (!system.Controllers.Keys.Contains(topControllerName))
        {
          system.Discover();
          Thread.Sleep(100);
        }

        // Wait until the top-controller state transition to 'Config' is complete
        topController = system.Controllers[topControllerName];
        topController.WaitState(Pmp.TopControllerState.Config, 60.0);

        // Apply configuration file
        var configurationFilesPath = "C:\\your\\path\\to\\persistent-configuration\\configuration-files\\";
        var configurationFile = configurationFilesPath + "configuration.xml";
        topController.LoadConfigurationFromFile(configurationFile);
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
int main()
{
    Pmp::PTopController topController;

    try
    {
        // Discover top-controller
        auto address = "localhost";
        auto system = std::make_shared<Pmp::CSystem>(address);

        auto topControllerName = "Arcas 5EG-1";
        while (system->GetControllers().count(topControllerName) == 0);
        {
            system->Discover();
            std::this_thread::sleep_for(std::chrono::milliseconds(100));
        }

        // Wait until the top-controller state transition to 'Config' is complete
        topController = system->GetController(topControllerName);
        topController->WaitState(Pmp::ETopControllerState::Config, 60.0);

        // Apply configuration file
        std::string configurationFilesPath = "C:\\your\\path\\to\\persistent-configuration\\configuration-files\\";
        auto configurationFile = configurationFilesPath + "configuration.xml";
        topController->LoadConfigurationFromFile(configurationFile);
        // end of configuration file validation
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
try:
    topController = None

    # Discover top-controller
    address = "localhost"
    system = Pmp.System(address)

    topControllerName = "Arcas 5EG-1"
    while topControllerName not in system.Controllers.Keys:
        system.Discover()
        time.sleep(0.1)

    # Wait until the top-controller state transition to "Config" is complete
    topController = system.Controllers[topControllerName]
    topController.WaitState(Pmp.TopControllerState.Config, 60.0)

    # Apply configuration file
    cygnus = topController.Controllers["Cygnus D3-400-4-2"]
    configurationFilesPath = "C:\\your\\path\\to\\persistent-configuration\\configuration-files\\"
    configurationFile = configurationFilesPath + "configuration.xml"
    topController.LoadConfigurationFromFile(configurationFile)
    # end of configuration file validation
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
address = 'localhost';
system = Pmp.System(address);

% TODO: REMOVE WHEN CYGNUS BECOMES AVAILABLE (MATLAB runs on MATLAB server, so we need to create a new simulator there)
systemDescription = 'C:\\your\\path\\to\\persistent-configuration\\configuration-files\\system.xml';
system.CreateControllersFromFile(systemDescription);

topControllerName = 'Arcas 5EG-1';
while ~any(strcmp(string(ToCell(system.Controllers.Keys)), topControllerName))
  system.Discover();
  pause(1);
end

% Wait until the top-controller state transition to 'Config' is complete
topController = system.Controllers.Item(topControllerName);
topController.WaitState(Pmp.TopControllerState.Config, 60.0);
% Apply configuration file
cygnus = topController.Controllers.Item("Cygnus D3-400-4-2");
configurationFilesPath = "C:\\your\\path\\to\\persistent-configuration\\configuration-files\\";
configurationFile = configurationFilesPath + "configuration.xml";
topController.LoadConfigurationFromFile(configurationFile);
% end of configuration file validation

Convert PMP configuration to persistent configuration binary

Open a console in the configuration folder, and run the PmpConfigToPersistentConfig tool with the following arguments:

Command to create persistent configuration files.
1
 C:\\your\\path\\to\\PmpConfigToPersistentConfig -p configuration.xml -c controller_testslave_cygnus.xml -o .

This generates the persistent_configuration.bin binary file.

Uploading and activating the persistent configuration

  1. Uploading and activating the binary file is a two stage approach. First, the binary is uploaded to the EtherCAT SubDevice through the corresponding updatable:

    1
    2
            // Upload generated persistent configuration file
            topController.GetByPath<Pmp.IUpdatable>("Cygnus D3-400-4-2/PersistentConfiguration/Configuration").LoadContentsFromFile(configurationFilesPath + "Cygnus D3-400-4-2-converted.bin");
    
    1
    2
    // Upload generated persistent configuration file
    topController->GetByPath<Pmp::PUpdatable>("Cygnus D3-400-4-2/PersistentConfiguration/Configuration")->LoadContentsFromFile(configurationFilesPath + "Cygnus D3-400-4-2-converted.bin");
    
    1
    2
        # Upload generated persistent configuration file
        topController.GetByPath[Pmp.IUpdatable]("Cygnus D3-400-4-2/PersistentConfiguration/Configuration").LoadContentsFromFile(configurationFilesPath + "Cygnus D3-400-4-2-converted.bin")
    
    1
    2
    % Upload generated persistent configuration file
    topController.Controllers.Item("Cygnus D3-400-4-2").ProcessingBlocks.Item("PersistentConfiguration").Updatables.Item("Configuration").LoadContentsFromFile(configurationFilesPath + "Cygnus D3-400-4-2-converted.bin");
    
  2. Afterwards, the EtherCAT SubDevice should be rebooted to activate the persistent configuration:

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
            // Restart subdevice
            INamed controllerToDisappear = null;
            ISubController appearedController = null;
            var childRemovedSub = system.SubscribeObjectChildRemoved((parent, child) => { if (child == controllerToDisappear) controllerToDisappear = null; });
            var childAddedSub = system.SubscribeObjectChildAdded((parent, child) =>
            {
                appearedController = child as ISubController;
            });
            var subdevice = topController.Controllers["Cygnus D3-400-4-2"];
            controllerToDisappear = subdevice;
            subdevice.Reboot();
    
            // Wait for subdevice to start rebooting
            while (controllerToDisappear != null)
                Thread.Sleep(100);
    
            // Wait for subdevice to be back after rebooting
            while (appearedController == null)
                Thread.Sleep(100);
    
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    // Restart subdevice
    Pmp::PNamed controllerToDisappear = nullptr;
    Pmp::PSubController appearedController = nullptr;
    auto childRemovedSub = system->SubscribeObjectChildRemoved([&](Pmp::PNamed parent, Pmp::PNamed child){ if (child == controllerToDisappear) controllerToDisappear = nullptr; });
    auto childAddedSub = system->SubscribeObjectChildAdded([&](Pmp::PNamed parent, Pmp::PNamed child)
    {
        appearedController = std::dynamic_pointer_cast<Pmp::ISubController>(child);
    });
    auto subdevice = topController->GetController("Cygnus D3-400-4-2");
    controllerToDisappear = subdevice;
    subdevice->Reboot();
    
    // Wait for subdevice to start rebooting
    while (controllerToDisappear != nullptr)
        std::this_thread::sleep_for(std::chrono::milliseconds(100));
    
    // Wait for subdevice to be back after rebooting
    while (appearedController == nullptr)
        std::this_thread::sleep_for(std::chrono::milliseconds(100));
    
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
        # Restart subdevice
        controllerToDisappear = None
        appearedController = None
    
        def ObjectChildRemoved(parent, child):
            global controllerToDisappear
            if child == controllerToDisappear:
                controllerToDisappear = None
    
        def ObjectChildAdded(parent, child):
            global appearedController
            appearedController =  topController.Controllers[child.Name]
    
        objectChildRemovedHandler = Pmp.ObjectChildRemovedHandler(lambda parent, child: ObjectChildRemoved(parent, child))
        childRemovedSub = system.SubscribeObjectChildRemoved(objectChildRemovedHandler)
        objectChildAddedHandler = Pmp.ObjectChildAddedHandler(lambda parent, child: ObjectChildAdded(parent, child))
        childAddedSub = system.SubscribeObjectChildAdded(objectChildAddedHandler)
    
        subdevice = topController.Controllers["Cygnus D3-400-4-2"]
        controllerToDisappear = subdevice
        subdevice.Reboot(False)
        appearedController = None
    
        # Wait for subdevice to start rebooting
        while controllerToDisappear != None:
            time.sleep(0.1)
    
        # Wait for subdevice to be back after rebooting
        while appearedController == None:
            time.sleep(0.1)
    
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    % Restart subdevice
    global controllerToDisappear;
    global appearedController;
    appearedController = [];
    
    % objectChildRemovedHandler = Pmp.ObjectChildRemovedHandler(@(parent, child) ObjectChildRemoved(parent, child));
    % childRemovedSub = system.SubscribeObjectChildRemoved(objectChildRemovedHandler);
    % objectChildAddedHandler = Pmp.ObjectChildAddedHandler(@(parent, child) ObjectChildAdded(parent, child));
    % childAddedSub = system.SubscribeObjectChildAdded(objectChildAddedHandler);
    
    subdevice = topController.Controllers.Item("Cygnus D3-400-4-2");
    controllerToDisappear = subdevice;
    subdevice.Reboot(false);
    
    % Wait for subdevice to start rebooting
    pause(2.0)
    
    % Wait for subdevice to be back after rebooting
    subdeviceName = "TestSlave-0";
    while ~any(strcmp(string(ToCell(topController.Controllers.Keys)), subdeviceName))
        pause(1.0)
    end
    
    appearedController = topController.Controllers.Item(subdeviceName);
    
  3. It can be verified the persistent configuration is active and there are no deltas between the actual values and the persistent configuration values:

    1
    2
    3
    4
    5
    6
            // Verify configuration is active
            if (appearedController.ProcessingBlocks["PersistentConfiguration"].Signals["Applied"].ValueUint32 != 1)
              return 2;
    
            if (appearedController.ProcessingBlocks["PersistentConfiguration"].Signals["Dirty"].ValueUint32 != 0)
              return 3;
    
    1
    2
    3
    4
    5
    6
    // Verify configuration is active
    if (appearedController->GetProcessingBlock("PersistentConfiguration")->GetSignal("Applied")->ReadUint32() != 1)
      return 2;
    
    if (appearedController->GetProcessingBlock("PersistentConfiguration")->GetSignal("Dirty")->ReadUint32() != 0)
      return 3;
    
    1
    2
    3
    4
    5
    6
        # Verify configuration is active
        if appearedController.ProcessingBlocks["PersistentConfiguration"].Signals["Applied"].ValueUint32 != 1:
            raise Exception("Persistent configuration is not applied")
    
        if appearedController.ProcessingBlocks["PersistentConfiguration"].Signals["Dirty"].ValueUint32 != 0:
            raise Exception("Persistent configuration is dirty")
    
    1
    2
    3
    4
    5
    6
    7
    8
    % Verify configuration is active
    if appearedController.ProcessingBlocks.Item("PersistentConfiguration").Signals.Item("Applied").ValueUint32 ~= 1
        quit(2)
    end
    
    if appearedController.ProcessingBlocks.Item("PersistentConfiguration").Signals.Item("Dirty").ValueUint32 ~= 0
        quit(3)
    end
    
  4. The persistent configuration can be removed by uploading an empty binary file:

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
            // Clear persistent configuration file
            appearedController.ProcessingBlocks["PersistentConfiguration"].Updatables["Configuration"].Contents = Array.Empty<byte>();
    
            // Restart subdevice
            controllerToDisappear = appearedController;
            appearedController.Reboot();
            appearedController = null;
    
    
            // Wait for subdevice to start rebooting
            while (controllerToDisappear != null)
                Thread.Sleep(100);
    
            // Wait for subdevice to be back after rebooting
            while (appearedController == null)
                Thread.Sleep(100);
    
            if (appearedController.ProcessingBlocks["PersistentConfiguration"].Signals["Applied"].ValueUint32 != 0)
              return 4;
    
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    // Clear persistent configuration file
    std::vector<uint8_t> empty(1,0);
    appearedController->GetProcessingBlock("PersistentConfiguration")->GetUpdatable("Configuration")->SetContents(empty);
    
    // Restart subdevice
    controllerToDisappear = appearedController;
    appearedController->Reboot();
    appearedController = nullptr;
    
    // Wait for subdevice to start rebooting
    while (controllerToDisappear != nullptr)
        std::this_thread::sleep_for(std::chrono::milliseconds(100));
    
    // Wait for subdevice to be back after rebooting
    while (appearedController == nullptr)
        std::this_thread::sleep_for(std::chrono::milliseconds(100));
    
    if (appearedController->GetProcessingBlock("PersistentConfiguration")->GetSignal("Applied")->ReadUint32() != 0)
      return 4;
    
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
        # Clear persistent configuration file
        appearedController.ProcessingBlocks["PersistentConfiguration"].Updatables["Configuration"].Contents = bytearray()
    
        # Restart subdevice
        controllerToDisappear = appearedController
        appearedController.Reboot(False)
        appearedController = None
    
        # Wait for subdevice to start rebooting
        while controllerToDisappear != None:
            time.sleep(0.1)
    
        # Wait for subdevice to be back after rebooting
        while appearedController == None:
            time.sleep(0.1)
    
        if appearedController.ProcessingBlocks["PersistentConfiguration"].Signals["Applied"].ValueUint32 != 0:
            raise Exception("Persistent configuration is still applied")
    
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    % Clear persistent configuration file
    appearedController.ProcessingBlocks.Item("PersistentConfiguration").Updatables.Item("Configuration").Contents = unicode2native(char(""), 'utf-8');
    
    % Restart subdevice
    controllerToDisappear = appearedController;
    appearedController.Reboot(false)
    
    % Wait for subdevice to start rebooting
    pause(3.0)
    
    while ~any(strcmp(string(ToCell(topController.Controllers.Keys)), subdeviceName))
        pause(1.0)
    end
    
    appearedController = topController.Controllers.Item(subdeviceName);
    
    if appearedController.ProcessingBlocks.Item("PersistentConfiguration").Signals.Item("Applied").ValueUint32 ~= 0
        quit(4)
    end