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.
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.
See also
- Load configuration file
Guide to load configuration via the PMP Tooling
- C++ API methods
- C# API methods
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.
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:
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¶
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");
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);
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
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