FORUM CTRLX AUTOMATION
ctrlX World Partner Apps for ctrlX AUTOMATION
Dear Community User! We are updating our platform to a new
system.
Read more: Important
information on the platform change.
01-26-2021 03:49 PM
Hey there,
I would like to extend the automation bundle example to read and write real time data. I have one snap imitating an EtherCat module. Therefore, the module creates 8 variables each to imitate leds and switches. The modified automation bundle example should do two task: 1) control 4 motors and 2) react to the switches and turn on the LEDs (switch1 -> led1, switch2 -> led2, ...). My problem is, that I cannot get the communication between the scheduler app and the fake EtherCat module. When I try to interact with realtime data of the fake EtherCat module from a snap as a dameon there is no problem, it just does not work from the scheduler application.
I have tried to add the dependencies in the CMakeList.txt files to access rt data. Maybe there is something missing. I could also imagen that there is something wrong in the snapcraft.yaml file. Are there any examples to create actions/tasks (executed by the scheduler) which access rt data? It would also be great, if there are any examples how to create custom taskes with custom actions from a C/C++ code base.
Does anyone know, where the problem could be? I can share my codebase or give more details, but I do not have any clue where to start.
Sincerly
Kai-Uwe
Solved! Go to Solution.
01-27-2021 08:26 AM - edited 01-27-2021 09:13 AM
The way you access to the realtime data in the scheduler example is quite similar then in the datalayer.realtime example. The main difference is that you do not connect to the ctrlX Data Layer via:
datalayer.factory()->createClient(DL_IPC_AUTO);
but you will use the service provided by the ctrlX Data Layer itself. You can do so because your code will be added as a bundle. To register a dependency to the service use following code in the activator.cpp. Please note that the name of the classes could differ:
// datalayer: cpp ServiceDependency w/ specifier 'name'
ServiceDependency<sdk::control::cmp_workshop, comm::datalayer::IDataLayerFactory>& serviceDepDl = component.createServiceDependency<comm::datalayer::IDataLayerFactory>(IDATALAYER_FACTORY_INTERFACE_NAME);
serviceDepDl.setVersionRange(IDATALAYER_CONSUMER_RANGE);
serviceDepDl.setRequired(true);
serviceDepDl.setStrategy(DependencyUpdateStrategy::suspend);
serviceDepDl.setCallbacks(&sdk::control::cmp_workshop::cppDlServiceStateChanged);
serviceDepDl.setCallbacks(&sdk::control::cmp_workshop::cppDlServiceAdded, &sdk::control::cmp_workshop::cppDlServiceRemoved);
In the callbacks created you can then handle over the service to your callableFactory:
std::shared_ptr<example::workshopCallableFactory> m_factory;
void cmp_workshop::cppDlServiceAdded(comm::datalayer::IDataLayerFactory* service) //Called automatically when callable added to task
{
m_datalayer_factory = service;
if (m_datalayer_factory)
{
m_factory->setDataLayerFactory(service); //Push DataLayerFactor service to m_factory
}
}
In your callableFactory start using it like you can see it in the datalayer.realtime example:
void workshopCallableFactory::setDataLayerFactory(comm::datalayer::IDataLayerFactory *datalayer_factory) //Called from cmp_workshop.cpp
{
m_datalayer_factory = datalayer_factory;
}
comm::datalayer::IClient *client = m_datalayer_factory->createClient();
The code that is called now in the cycle time you defined in the task can be found in the callable.cpp:
case common::scheduler::SchedEventType::SCHED_EVENT_TICK:
{
// Do cyclic stuff
// Better do not use something like std::cout, use trace instead.
TRACE_MSG("Scheduler event tick");
return common::scheduler::SchedEventResponse::SCHED_EVENT_RESP_OKAY;
}
01-28-2021 11:48 AM - last edited on 01-29-2021 10:53 AM by CodeShepherd
Hello,
result = datalayer.factory()->openMemory(output_led, "fakeEtherCat/rt/led");
The ctrlX Core switches in Service Mode and there is no error message (or at least I didn't find one). So I tried to use the m_datalayer_factory to open the memory and the programm got one step further. After that it failed at line:
result = output_led->getMemoryMap(data_led);
The result message says: "Getting MemoryMap with: DL_RT_INVALIDMEMORYMAP" and the log shows an error in AppArmor:
AVC apparmor="DENIED" operation="open" profile="snap.rexroth-automationcore.control" name="/dev/shm/snap.fakeethercat.f24fa964-4376-0039-0d4b-deadbe000001" pid=10989 comm="dl_client" requested_mask="wr" denied_mask="wr" fsuid=0 ouid=0
Another question, which configuration do I need to use for the provider in the fake-etherCat module? Should I use DL_IPC_AUTO oder leave the parameter out? Currently, I use it like this:
comm::datalayer::IProvider *provider = datalayer.factory()->createProvider("tcp://boschrexroth:boschrexroth@192.168.1.1:2070");
Sincerly
Kai-Uwe
Edit: It is maybe also worth mentioning that synchronous reading works fine
result = client->readSync("fakeEtherCat/rt/led/map", &data_led);
01-29-2021 10:52 AM
You can use the DL_IPC_AUTO for better performance for accesses because you are on the same control with user and owner but it doesn't really matter in your test.
For your problem in our opinion your owner does not set up the memory map correctly so the user cannot read it. Please have a look in the datalayer.relatime example at the function createMemMap() in the main_owner.cpp for more information how to do so.
You could also add your code to a post, so we can have a look at it.
01-29-2021 12:05 PM
Thank you for your answer, I think most of the code should be close to the example.
In the main function I will set up the rt data for the LEDs and switches and init them with zero.
comm::datalayer::DatalayerSystem datalayer;
comm::datalayer::DlResult result;
comm::datalayer::Variant data;
// Starts the ctrlX Data Layer system without a new broker because one broker is already running on ctrlX device
datalayer.start(false);
std::cout << "Register 'etherCat' as root element with 8 nodes 'switch' and 8 nodes 'led'" << std::endl;
// Creates a provider at Data Layer backend to provide data to Data Layer clients
comm::datalayer::IProvider *provider = datalayer.factory()->createProvider("tcp://boschrexroth:boschrexroth@192.168.1.1:2070");
result = provider->start();
if (comm::datalayer::STATUS_FAILED(result))
{
std::cout << "Starting provider failed with: " << result.toString() << std::endl;
std::cout << "... Exiting" << std::endl;
return -1;
}
std::cout << "creating realtime memory" << std::endl;
std::shared_ptr<comm::datalayer::IMemoryOwner> leds;
result = datalayer.factory()->createMemorySync(leds, "fakeEtherCat/rt/led", provider, 8, comm::datalayer::MemoryType_Input);
if (comm::datalayer::STATUS_FAILED(result))
std::cout << "creation of leds failed with: " << result.toString() << std::endl;
std::cout << "creating realtime memory" << std::endl;
std::shared_ptr<comm::datalayer::IMemoryOwner> switches;
result = datalayer.factory()->createMemorySync(switches, "fakeEtherCat/rt/switch", provider, 8, comm::datalayer::MemoryType_Output);
if (comm::datalayer::STATUS_FAILED(result))
std::cout << "creation of switches failed with: " << result.toString() << std::endl;
comm::datalayer::Variant memMap = createMemMap(8, "led");
std::cout << "Set memory map for leds and switches buffer" << std::endl;
result = leds->setMemoryMap(memMap);
if (comm::datalayer::STATUS_FAILED(result))
std::cout << "Set leds memMap failed with: " << result.toString() << std::endl;
memMap = createMemMap(8, "switch");
result = switches->setMemoryMap(memMap);
if (comm::datalayer::STATUS_FAILED(result))
std::cout << "Set switches memMap failed with: " << result.toString() << std::endl;
{
uint8_t *inData;
result = leds->beginAccess(inData, 0);
if (comm::datalayer::STATUS_SUCCEEDED(result))
{
std::cout <<"fill led memory" << std::endl;
memset(inData, 0, 8);
leds->endAccess();
}
else{
std::cout <<"fill memory failed with: " << result.toString() << std::endl;
}
}
{
uint8_t *inData;
result = switches->beginAccess(inData, 0);
if (comm::datalayer::STATUS_SUCCEEDED(result))
{
std::cout <<"fill switch memory" << std::endl;
memset(inData, 0, 8);
switches->endAccess();
}
else{
std::cout <<"fill memory failed with: " << result.toString() << std::endl;
}
}
After this the function will go on in a while loop without any code except thread.sleep with 2s timer.
I changed the createMemMap function to create boolean variables
comm::datalayer::Variant createMemMap(size_t varCount, std::string prefix)
{
flatbuffers::FlatBufferBuilder builder;
std::vector<flatbuffers::Offset<comm::datalayer::Variable>> vecVariables;
for (uint32_t i = 0; i < varCount; i++)
{
std::string varName = prefix + std::to_string(i+1);
char name[varName.length() + 1];
strcpy(name, varName.c_str());
auto variable = comm::datalayer::CreateVariableDirect(builder,
name, // name of variable (has to be unique), can be divided by "/" for hierarchical structure
8 * i, // bit offset of variable in memory
8, // size of variable in bits
comm::datalayer::TYPE_DL_BOOL8.c_str()
);
vecVariables.push_back(variable);
}
auto variables = builder.CreateVectorOfSortedTables(&vecVariables);
comm::datalayer::MemoryMapBuilder memmap(builder);
memmap.add_revision(0);
memmap.add_variables(variables);
auto memmapFinished = memmap.Finish();
builder.Finish(memmapFinished);
comm::datalayer::Variant result;
result.copyFlatbuffers(builder);
return result;
}
I will try to compare the example to my version and find any changes, but maybe you can spot it easily.
Thank you for your help.
Sincerly
Kai-Uwe
02-01-2021 05:34 PM - edited 02-01-2021 05:36 PM
Sorry for the late answer, but there is quite a lot to do at the moment.
Your code first of all looks good. I will have to do some tests.
So just to be sure your system setup is:
- A ctrlX CORE or ctrlX COREvirtual?
- Your system apps and SDK version is 1.6.? ?
- A own app written by you that provides data in a non cyclic task and then accesses it cyclically as a bundle.
- Your callable is added to a task in the scheduler. How does your setting looks like there?
02-02-2021 08:51 AM
My specs are:
- ctrlX COREvirtual
- My system apps and SDK version is 1.6.1:
apps:
- Motion 1.6.0
systemapps:
- core 16.2.47.1
- core18 - 20200929
- pc - 18-2
- pc-kernel - 4.15.0-124.127
- Automation Core - 1.6.0
- Device Admin - 1.6.0
- Solutions - 1.6.0
- snapd - 2.47.1
- A own app written by you that provides data in a non cyclic task and then accesses it cyclically as a bundle: yes
The fakeEtherCat app should imitate the etherCat by creating the nodes for the LEDs and switches as boolean
The MotionLedController should run cyclically as a bundle and check the switch-variables each cycle and edit the LED variables
- Your callable is added to a task in the scheduler. How does your setting looks like there?
Task: Name: TestTask, Cycletime: 2000us, Priority: 11, Event: cyclic
Callable: Name HelloCallableFactory, Run-Index: 0, Arguments empty
02-04-2021 02:27 PM - edited 02-04-2021 02:39 PM
After the analysis of your code we found that it is at the moment not possible to be owner of real time data in an own process in a app. Actually the owner has to be implemented as a bundle in our system but the user can be integrated and separate (but then using the system-files interface like mentioned in the datalayer.ecat.io example).
We are going to implement the necessary feature in our march release (RM20.03 version: 1.8). Thanks for bringing this up.