cancel
Showing results for 
Search instead for 
Did you mean: 
SOLVED

How to access RT Data from Scheduler app

How to access RT Data from Scheduler app

KaiUwe
Member

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

7 REPLIES 7

CodeShepherd
Community Moderator
Community Moderator

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;
  }

Hello,

first I want to thank you for your answer, it helped me a lot! Unfortunatly it is still not working. 
When I try to access the RT data the app crashes while executing the following line:

 

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);

 

CodeShepherd
Community Moderator
Community Moderator

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.

 

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

CodeShepherd
Community Moderator
Community Moderator

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?

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

CodeShepherd
Community Moderator
Community Moderator

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.

Icon--AD-black-48x48Icon--address-consumer-data-black-48x48Icon--appointment-black-48x48Icon--back-left-black-48x48Icon--calendar-black-48x48Icon--center-alignedIcon--Checkbox-checkIcon--clock-black-48x48Icon--close-black-48x48Icon--compare-black-48x48Icon--confirmation-black-48x48Icon--dealer-details-black-48x48Icon--delete-black-48x48Icon--delivery-black-48x48Icon--down-black-48x48Icon--download-black-48x48Ic-OverlayAlertIcon--externallink-black-48x48Icon-Filledforward-right_adjustedIcon--grid-view-black-48x48IC_gd_Check-Circle170821_Icons_Community170823_Bosch_Icons170823_Bosch_Icons170821_Icons_CommunityIC-logout170821_Icons_Community170825_Bosch_Icons170821_Icons_CommunityIC-shopping-cart2170821_Icons_CommunityIC-upIC_UserIcon--imageIcon--info-i-black-48x48Icon--left-alignedIcon--Less-minimize-black-48x48Icon-FilledIcon--List-Check-grennIcon--List-Check-blackIcon--List-Cross-blackIcon--list-view-mobile-black-48x48Icon--list-view-black-48x48Icon--More-Maximize-black-48x48Icon--my-product-black-48x48Icon--newsletter-black-48x48Icon--payment-black-48x48Icon--print-black-48x48Icon--promotion-black-48x48Icon--registration-black-48x48Icon--Reset-black-48x48Icon--right-alignedshare-circle1Icon--share-black-48x48Icon--shopping-bag-black-48x48Icon-shopping-cartIcon--start-play-black-48x48Icon--store-locator-black-48x48Ic-OverlayAlertIcon--summary-black-48x48tumblrIcon-FilledvineIc-OverlayAlertwhishlist