Beckhoff / ADS

Beckhoff protocol to communicate with TwinCAT devices.
MIT License
491 stars 193 forks source link

Synchronous vs Async for reading and writing to ADS #191

Closed captain-yoshi closed 10 months ago

captain-yoshi commented 1 year ago

I currently use multiple AdsVariable (c++) for each of the TwinCAT variables below:

VAR
    AdsWriteTorques : ARRAY[1..6] OF LREAL;

    AdsReadPositions : ARRAY[1..6] OF LREAL;
    AdsReadVelocities : ARRAY[1..6] OF LREAL;
    AdsReadFlagStartController : BOOL;
END_VAR

This is simple and easy to implement, but I think it writes and read data synchronously. The more I add AdsVariable and read or write to them, the more time it takes. Is this assumption true ?


Solution 1 I could still use the AdsVariable as a container for all of my data. Well one for reading and another for writing. Whatever the number of variables, it only blocks my program 2 times : once to read and once to write.

Solution 2 Use the AdsNotification from here. Can I write a value in the callback ?

typedef void (* PAdsNotificationFuncExLegacy)(AmsAddr* pAddr, AdsNotificationHeader* pNotification, uint32_t hUser);

With this solution, I would not loose time for reading my variables as it would be non-blocking. Is there a non blocking API for writing values ?

pbruenn commented 1 year ago

I think we have a different understanding of what async means. In my world async means I have multiple threads in my process. In C++ you have at least two helpers to create async behaviour:

  1. std::thread: you could create a communication thread, taking care of your variable updates
  2. std::async: update a variable in a "background" thread

AdsNotifications are a different concept, which I would refer to as "event handling". The idea is to avoid polling for variable updates in the PLC, but instead be "called back", when the value changed (on the PLC).

To me your real problem sounds not really about async variable updates, but efficiency. The problem with our convenient AdsVariable is, that it requires an ADS message for every variable. The most effective way of handling many variables in one (or a few) ADS messages is ADS-sum command

captain-yoshi commented 1 year ago

You are right, efficiency is the key to my problem.

With the AdsDevice API, I can use the AdsSyncReadWriteReqEx2 method. But this only works for one variable right ? I would have to use an array or a struct to be efficient. And using a struct is not really portable because of padding.

The proposed ADS-sum command is really neat. The closest thing to achieve that would be to fill an AmsRequest or is there a better way ?

        AmsRequest request {
            *pAddr,
            (uint16_t)port,
            AoEHeader::READ_WRITE,
            readLength,
            readData,
            bytesRead,
            sizeof(AoEReadWriteReqHeader) + writeLength
        };
        request.frame.prepend(writeData, writeLength);
        request.frame.prepend(AoEReadWriteReqHeader {
            indexGroup,
            indexOffset,
            readLength,
            writeLength
        });
captain-yoshi commented 1 year ago

@pbruenn Can I handle many variables (read + write) on a single ADS message ? Is it possible using this library ? If so, can you provide a minimal example ?

Was only able to read and write with 2 ADS messages.

yannickschrade commented 1 year ago

@captain-yoshi the infosys provides an example project for the ADS-Sum command https://infosys.beckhoff.com/english.php?content=../content/1033/tc3_adssamples_net/185258507.html&id=8424732030635156090. I think that's exactly what you're loking for.

captain-yoshi commented 1 year ago

Seems to work using ReadWriteReqEx2, no errors are returned from the AdsDevice (route_ variable)

auto error = route_->ReadWriteReqEx2(
    ADSIGRP_SUMUP_READWRITE, // uint32_t indexGroup
    total_subcommands,            // uint32_t indexOffset -> amount of ADS request in
                      // sum command
    8,                // size_t readLength
    &buffer_state_,   // void *readData
    write_index,      // size_t writeLength
    &buffer_command_, // const void *writeData
    &bytes_read);     // uint32_t *bytesRead

if (error) {
  throw AdsException(error);
}

std::cout << "bytes read = " << bytes_read << std::endl;

// Point to ADS error return code
PBYTE pObjAdsErrRes = (BYTE *)buffer_state_;

for (std::size_t i = 0; i < total_subcommands; ++i) {

  // was communication for ADS-sub command OK ??
  int32_t nAdsErr = *(int32_t *)pObjAdsErrRes;
  if (nAdsErr == 0) {
    // data are written to device
    std::cout << "no errors for subcommand " << i << std::endl;
  } else {
    // error writing this data to device
    std::cout << "error for subcommand " << i << std::endl;
    throw AdsException(nAdsErr);
  }

  pObjAdsErrRes = pObjAdsErrRes + 4 + 4; // point to next ADS-err object
}

But after checking the subcommands responses, I get this error

bytes read = 16
error for subcommand 0
terminate called after throwing an instance of 'AdsException'
  what():  Ads operation failed with error code 1793.

Which resolves to: Service is not supported by the server.

How do I resolve this issue ? Do I need to to enable this service in TwinCAT ?

soberschmidt commented 1 year ago

In general ADS services are implemented (=supported) by an ADS server or not. There is no need to enable it in TwinCAT. ADS error 1793 (service not supported) shows that the ADS server is running but the "service", which are addressed via index group and offset is not implemented. Please share the parameters of your target port, index group and offset of your ADS sub command.

Further samples for for the Windows C++ API (TcAdsDll.dll) about sum command can be found here.