Beckhoff / ADS

Beckhoff protocol to communicate with TwinCAT devices.
MIT License
502 stars 194 forks source link

Porting over to ROS #96

Closed kapkunal closed 3 years ago

kapkunal commented 5 years ago

Hi, I am not very much familiar with either ROS or ADS. We need to have a ROS node talking to Beckhoff over ADS. I am guessing ROS build system is a bit different. How can I port this library to ROS?

pbruenn commented 5 years ago

Well, I can only guess you mean https://www.ros.org/. I don't know their build system either. As we are able to build for Windows, Linux, macOS and FreeBSD, I am pretty sure it wouldn't be to difficult for you to convert our 64 lines Makefile for ROS.

jstiefel commented 4 years ago

Hi,

You can just copy the source files in AdsLib to your workspace and add them as a library to your Cmake. Make sure to include the header files.

amirtronics commented 11 months ago

Hi,

I've copied the source files and included the directory into the ros packae cmake but I'm getting this error:

/usr/bin/ld: CMakeFiles/bk_example.dir/src/bk_example.cpp.o: in functionNotifyCallback(AmsAddr const, AdsNotificationHeader const, unsigned int)': bk_example.cpp:(.text+0xc0): undefined reference to operator<<(std::ostream&, AmsNetId const&)' /usr/bin/ld: CMakeFiles/bk_example.dir/src/bk_example.cpp.o: in functionreadStateExample(std::ostream&, AdsDevice const&)': bk_example.cpp:(.text+0xbf1): undefined reference to AdsDevice::GetState() const' /usr/bin/ld: CMakeFiles/bk_example.dir/src/bk_example.cpp.o: in functionrunExample(std::ostream&)': bk_example.cpp:(.text+0xcec): undefined reference to AmsNetId::AmsNetId(unsigned char, unsigned char, unsigned char, unsigned char, unsigned char, unsigned char)' /usr/bin/ld: bk_example.cpp:(.text+0xd8d): undefined reference toAdsDevice::AdsDevice(std::cxx11::basic_string<char, std::char_traits, std::allocator > const&, AmsNetId, unsigned short)' /usr/bin/ld: CMakeFiles/bk_example.dir/src/bk_example.cpp.o: in function `AdsNotification::AdsNotification(AdsDevice const&, std::cxx11::basic_string<char, std::char_traits, std::allocator > const&, AdsNotificationAttrib const&, void ()(AmsAddr const, AdsNotificationHeader const, unsigned int), unsigned int)': bk_example.cpp:(.text._ZN15AdsNotificationC2ERK9AdsDeviceRKNSt7cxx1112basic_stringIcSt11char_traitsIcESaIcEEERK21AdsNotificationAttribPFvPK7AmsAddrPK21AdsNotificationHeaderjEj[_ZN15AdsNotificationC5ERK9AdsDeviceRKNSt7cxx1112basic_stringIcSt11char_traitsIcESaIcEEERK21AdsNotificationAttribPFvPK7AmsAddrPK21AdsNotificationHeaderjEj]+0x47): undefined reference to `AdsDevice::GetHandle(std::cxx11::basic_string<char, std::char_traits, std::allocator > const&) const' /usr/bin/ld: bk_example.cpp:(.text._ZN15AdsNotificationC2ERK9AdsDeviceRKNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEERK21AdsNotificationAttribPFvPK7AmsAddrPK21AdsNotificationHeaderjEj[_ZN15AdsNotificationC5ERK9AdsDeviceRKNSt7cxx1112basic_stringIcSt11char_traitsIcESaIcEEERK21AdsNotificationAttribPFvPK7AmsAddrPK21AdsNotificationHeaderjEj]+0x86): undefined reference to `AdsDevice::GetHandle(unsigned int, unsigned int, AdsNotificationAttrib const&, void ()(AmsAddr const, AdsNotificationHeader const, unsigned int), unsigned int) const' /usr/bin/ld: CMakeFiles/bk_example.dir/src/bk_example.cpp.o: in function AdsNotification::AdsNotification(AdsDevice const&, unsigned int, unsigned int, AdsNotificationAttrib const&, void (*)(AmsAddr const*, AdsNotificationHeader const*, unsigned int), unsigned int)': bk_example.cpp:(.text._ZN15AdsNotificationC2ERK9AdsDevicejjRK21AdsNotificationAttribPFvPK7AmsAddrPK21AdsNotificationHeaderjEj[_ZN15AdsNotificationC5ERK9AdsDevicejjRK21AdsNotificationAttribPFvPK7AmsAddrPK21AdsNotificationHeaderjEj]+0x44): undefined reference toAdsDevice::GetHandle(unsigned int) const' /usr/bin/ld: bk_example.cpp:(.text._ZN15AdsNotificationC2ERK9AdsDevicejjRK21AdsNotificationAttribPFvPK7AmsAddrPK21AdsNotificationHeaderjEj[_ZN15AdsNotificationC5ERK9AdsDevicejjRK21AdsNotificationAttribPFvPK7AmsAddrPK21AdsNotificationHeaderjEj]+0x6e): undefined reference to AdsDevice::GetHandle(unsigned int, unsigned int, AdsNotificationAttrib const&, void (*)(AmsAddr const*, AdsNotificationHeader const*, unsigned int), unsigned int) const' /usr/bin/ld: CMakeFiles/bk_example.dir/src/bk_example.cpp.o: in functionAdsVariable::AdsVariable(AdsDevice const&, unsigned int, unsigned int)': bk_example.cpp:(.text._ZN11AdsVariableIhEC2ERK9AdsDevicejj[_ZN11AdsVariableIhEC5ERK9AdsDevicejj]+0x54): undefined reference to AdsDevice::GetHandle(unsigned int) const' /usr/bin/ld: CMakeFiles/bk_example.dir/src/bk_example.cpp.o: in functionAdsVariable::AdsVariable(AdsDevice const&, std::cxx11::basic_string<char, std::char_traits, std::allocator > const&)': bk_example.cpp:(.text._ZN11AdsVariableIhEC2ERK9AdsDeviceRKNSt7cxx1112basic_stringIcSt11char_traitsIcESaIcEEE[_ZN11AdsVariableIhEC5ERK9AdsDeviceRKNSt7cxx1112basic_stringIcSt11char_traitsIcESaIcEEE]+0x54): undefined reference to `AdsDevice::GetHandle(std::cxx11::basic_string<char, std::char_traits, std::allocator > const&) const' /usr/bin/ld: CMakeFiles/bk_example.dir/src/bk_example.cpp.o: in function AdsVariable<std::array<unsigned char, 4ul> >::AdsVariable(AdsDevice const&, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&)': bk_example.cpp:(.text._ZN11AdsVariableISt5arrayIhLm4EEEC2ERK9AdsDeviceRKNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEE[_ZN11AdsVariableISt5arrayIhLm4EEEC5ERK9AdsDeviceRKNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEE]+0x54): undefined reference toAdsDevice::GetHandle(std::__cxx11::basic_string<char, std::char_traits, std::allocator > const&) const' /usr/bin/ld: CMakeFiles/bk_example.dir/src/bk_example.cpp.o: in function AdsVariable<unsigned char>::Read(unsigned long, void*) const': bk_example.cpp:(.text._ZNK11AdsVariableIhE4ReadEmPv[_ZNK11AdsVariableIhE4ReadEmPv]+0x69): undefined reference toAdsDevice::ReadReqEx2(unsigned int, unsigned int, unsigned long, void, unsigned int) const' /usr/bin/ld: CMakeFiles/bk_example.dir/src/bk_example.cpp.o: in function AdsVariable<unsigned char>::Write(unsigned long, void const*) const': bk_example.cpp:(.text._ZNK11AdsVariableIhE5WriteEmPKv[_ZNK11AdsVariableIhE5WriteEmPKv]+0x4c): undefined reference toAdsDevice::WriteReqEx(unsigned int, unsigned int, unsigned long, void const) const' /usr/bin/ld: CMakeFiles/bk_example.dir/src/bk_example.cpp.o: in function `AdsVariable<std::array<unsigned char, 4ul> >::Write(unsigned long, void const) const': bk_example.cpp:(.text._ZNK11AdsVariableISt5arrayIhLm4EEE5WriteEmPKv[_ZNK11AdsVariableISt5arrayIhLm4EEE5WriteEmPKv]+0x4c): undefined reference to AdsDevice::WriteReqEx(unsigned int, unsigned int, unsigned long, void const*) const' /usr/bin/ld: CMakeFiles/bk_example.dir/src/bk_example.cpp.o: in functionAdsVariable<std::array<unsigned char, 4ul> >::Read(unsigned long, void) const': bk_example.cpp:(.text._ZNK11AdsVariableISt5arrayIhLm4EEE4ReadEmPv[_ZNK11AdsVariableISt5arrayIhLm4EEE4ReadEmPv]+0x69): undefined reference to `AdsDevice::ReadReqEx2(unsigned int, unsigned int, unsigned long, void, unsigned int*) const' collect2: error: ld returned 1 exit status make[2]: [beckhoff_ros/CMakeFiles/bk_example.dir/build.make:281: /home/amire/catkin_ws/devel/lib/beckhoff_ros/bk_example] Error 1 make[1]: [CMakeFiles/Makefile2:4138: beckhoff_ros/CMakeFiles/bk_example.dir/all] Error 2 make: *** [Makefile:141: all] Error 2 Invoking "make -j8 -l8" failed `

jstiefel commented 11 months ago

@roboticswithamir That's pretty hard to read. Do you build the AdsLib before and link it correctly in your CMakeFiles?

kapkunal commented 11 months ago

Wow it’s been a while! I had it working. If I could find my code, I will respond. It was very straight forward except configuring cmakelist

amirtronics commented 11 months ago

Hey @jstiefel and @kapkunal. So this is the main code which is just a modified version of ADS example.cpp:

#include <ros/ros.h>
#include <std_msgs/String.h>
#include "AdsLib.h"
#include "AdsNotificationOOI.h"
#include "AdsVariable.h"

#include <array>
#include <cstring>
#include <iostream>
#include <iomanip>

using ExampleDatatype = uint8_t;

static ros::Publisher ads_publisher;

static void NotifyCallback(const AmsAddr* pAddr, const AdsNotificationHeader* pNotification, uint32_t hUser)
{
    ExampleDatatype data{};
    std::memcpy(&data, pNotification + 1, std::min<size_t>(sizeof(data), pNotification->cbSampleSize));
    std::cout << std::setfill('0') <<
        "NetId: " << pAddr->netId <<
        " hUser 0x" << std::hex << hUser <<
        " sample time: " << std::dec << pNotification->nTimeStamp <<
        " sample size: " << std::dec << pNotification->cbSampleSize <<
        " value:" << " 0x" << std::hex << +data <<
        '\n';
}

static void notificationExample(std::ostream& out, const AdsDevice& route)
{
    const AdsNotificationAttrib attrib = {
        sizeof(ExampleDatatype),
        ADSTRANS_SERVERCYCLE,
        0,
        {4000000}
    };
    AdsNotification notification { route, 0x4020, 4, attrib, &NotifyCallback, 0xDEADBEEF };

    out << "Hit ENTER to stop notifications\n";
    std::cin.ignore();
}

static void notificationByNameExample(std::ostream& out, const AdsDevice& route)
{
    const AdsNotificationAttrib attrib = {
        sizeof(ExampleDatatype),
        ADSTRANS_SERVERCYCLE,
        0,
        {4000000}
    };

    out << __FUNCTION__ << "():\n";
    AdsNotification notification { route, "MAIN.byByte[4]", attrib, &NotifyCallback, 0xBEEFDEAD };

    out << "Hit ENTER to stop by name notifications\n";
    std::cin.ignore();
}

static void readExample(std::ostream& out, const AdsDevice& route)
{
    AdsVariable<uint8_t> readVar {route, 0x4020, 0};

    out << __FUNCTION__ << "():\n";
    for (size_t i = 0; i < 8; ++i) {
        out << "ADS read " << std::hex << (uint32_t)readVar << '\n';
    }
}

static void readByNameExample(std::ostream& out, const AdsDevice& route)
{
    AdsVariable<uint8_t> readVar {route, "MAIN.byByte[4]"};

    out << __FUNCTION__ << "():\n";
    for (size_t i = 0; i < 8; ++i) {
        out << "ADS read " << std::hex << (uint32_t)readVar << '\n';
    }
}

static void readWriteExample(std::ostream& out, const AdsDevice& route)
{
    AdsVariable<uint8_t> simpleVar {route, "MAIN.byByte[0]"};
    AdsVariable<uint8_t> validation {route, "MAIN.byByte[0]"};

    out << __FUNCTION__ << "():\n";
    simpleVar = 0xA5;
    out << "Wrote " << 0xA5 << " to MAIN.byByte and read " << (uint32_t)validation << " back\n";
    simpleVar = 0x5A;
    out << "Wrote " << (uint32_t)simpleVar << " to MAIN.byByte and read " << (uint32_t)validation << " back\n";
}

static void readWriteArrayExample(std::ostream& out, const AdsDevice& route)
{
    static const std::array<uint8_t, 4> arrayToWrite = { 1, 2, 3, 4 };
    AdsVariable<std::array<uint8_t, 4> > arrayVar {route, "MAIN.byByte"};
    arrayVar = arrayToWrite;
    std::array<uint8_t, 4> readArray = arrayVar;
    out << "Wrote array with first value " << (uint32_t)arrayToWrite[0] << " and last value " <<
        (uint32_t)arrayToWrite[3] << "\n";
    out << "Read back array with first value " << (uint32_t)readArray[0] << " and last value " <<
        (uint32_t)readArray[3] << "\n";
}

static void readStateExample(std::ostream& out, const AdsDevice& route)
{
    const auto state = route.GetState();

    out << "ADS state: " << std::dec << (uint16_t)state.ads << " devState: " << std::dec << (uint16_t)state.device <<
        '\n';
}

static void runExample(std::ostream& out)
{
    static const AmsNetId remoteNetId { 192, 168, 0, 231, 1, 1 };
    static const char remoteIpV4[] = "ads-server";

    // uncomment and adjust if automatic AmsNetId deduction is not working as expected
    //bhf::ads::SetLocalAddress({192, 168, 0, 1, 1, 1});

    AdsDevice route {remoteIpV4, remoteNetId, AMSPORT_R0_PLC_TC3};
    notificationExample(out, route);
    notificationByNameExample(out, route);
    readExample(out, route);
    readByNameExample(out, route);
    readWriteExample(out, route);
    readWriteArrayExample(out, route);
    readStateExample(out, route);
}

int main(int argc, char** argv)
{
    ros::init(argc, argv, "ros_ads_node");
    ros::NodeHandle nh;

    ads_publisher = nh.advertise<std_msgs::String>("ads_data", 1000);

    try 
    {
        runExample(std::cout);
    } 
    catch (const AdsException& ex) 
    {
        std::cout << "Error: " << ex.errorCode << "\n";
        std::cout << "AdsException message: " << ex.what() << "\n";
    } 
    catch (const std::runtime_error& ex) 
    {
        std::cout << ex.what() << '\n';
    }
    std::cout << "Hit ENTER to continue\n";
    std::cin.ignore();

    ros::spin();

    return 0;
}

And this is the CMakeLists.txt:

cmake_minimum_required(VERSION 3.0.2)
project(beckhoff_ros)

find_package(catkin REQUIRED COMPONENTS roscpp rospy std_msgs tf image_transport)
find_package(cv_bridge REQUIRED)
find_package(sensor_msgs REQUIRED)
find_package(geometry_msgs REQUIRED)
find_package(OpenCV REQUIRED)
find_package(message_generation REQUIRED)
find_package(genmsg REQUIRED)
find_package(Eigen3 REQUIRED)
find_package(Threads)

message(STATUS "OpenCV Include Directory: ${OpenCV_INCLUDE_DIRS}")

# Message Declaration #
generate_messages(DEPENDENCIES std_msgs geometry_msgs sensor_msgs)

add_library(AdsLib ADS/AdsLib/AdsLib.cpp)

catkin_package(
#  INCLUDE_DIRS include
#  LIBRARIES bebop_ros
#  CATKIN_DEPENDS roscpp rospy std_msgs
#  DEPENDS system_lib
)

include_directories(
  include
  ${catkin_INCLUDE_DIRS}
  ${OpenCV_INCLUDE_DIRS}
  /home/amire/catkin_ws/src/beckhoff_ros/ADS/
  /home/amire/catkin_ws/src/beckhoff_ros/ADS/AdsLib
  /usr/local/include
  ../AdsLib/
)

add_executable(bk_example src/bk_example.cpp)
target_link_libraries(bk_example ${catkin_LIBRARIES})
target_link_libraries(bk_example ${cv_bridge_LIBRARIES})
target_link_libraries(bk_example ${OpenCV_LIBS})
target_link_libraries(bk_example AdsLib)

And I'm getting this error now:

/usr/bin/ld: CMakeFiles/bk_example.dir/src/bk_example.cpp.o: in functionNotifyCallback(AmsAddr const, AdsNotificationHeader const, unsigned int)': bk_example.cpp:(.text+0xc0): undefined reference to operator<<(std::ostream&, AmsNetId const&)' collect2: error: ld returned 1 exit status make[2]: *** [beckhoff_ros/CMakeFiles/bk_example.dir/build.make:281: /home/amire/catkin_ws/devel/lib/beckhoff_ros/bk_example] Error 1 make[1]: *** [CMakeFiles/Makefile2:4681: beckhoff_ros/CMakeFiles/bk_example.dir/all] Error 2 make[1]: *** Waiting for unfinished jobs.... [ 98%] Linking CXX executable /home/amire/catkin_ws/devel/lib/ros_ads_node/ros_ads_node [100%] Built target ros_ads_node make: *** [Makefile:141: all] Error 2 Invoking "make -j8 -l8" failed

amirtronics commented 11 months ago

I know people have already published a ROS package for ADS over #165 . But I was wondering what's the routine way to integrate the AMS cmake files inot my own on ROS?

jstiefel commented 11 months ago

I think you missed to add the corresponding files. You get some undefined references, which are not related to ROS. Try to include the other classes as well - they are necessary, e.g.: AdsDevice.cpp, AdsVariable.cpp, etc.

I created a catkin package some years ago which pulls and installs the AdsLib at the correct location in your workspace. However, ADS was restructured in the meantime. If you want to use the latest version of ADS, you would need to adapt it. If you do, please create a PR :-): https://github.com/ethz-msrl/ads_catkin

amirtronics commented 11 months ago

Thanks a lot Julian! I forked your repo and made some changes and it's working now. The PR has also been created. One thing remains mystery though, in your wrapper you used the following GIT_TAG:

download_project(
    PROJ    ads
    PREFIX ${CATKIN_DEVEL_PREFIX}/ads
    GIT_REPOSITORY https://github.com/Beckhoff/ADS.git
    GIT_TAG 6b3a03009a757cf651fe44d8be7b6df698028f0e
    # GIT_TAG master
)

However, I checked all Beckhoff releases and this repo doesn't exist anymore. So, how is it still able to grab and clone the repo with the old GIT_TAG?

jstiefel commented 11 months ago

Thanks a lot Julian! I forked your repo and made some changes and it's working now. The PR has also been created. One thing remains mystery though, in your wrapper you used the following GIT_TAG:

download_project(
    PROJ    ads
    PREFIX ${CATKIN_DEVEL_PREFIX}/ads
    GIT_REPOSITORY https://github.com/Beckhoff/ADS.git
    GIT_TAG 6b3a03009a757cf651fe44d8be7b6df698028f0e
    # GIT_TAG master
)

However, I checked all Beckhoff releases and this repo doesn't exist anymore. So, how is it still able to grab and clone the repo with the old GIT_TAG?

Happy to help and thanks for the PR. The GIT_TAG is just referencing the latest commit that was available at that time. It was probably not a release: https://github.com/Beckhoff/ADS/tree/6b3a03009a757cf651fe44d8be7b6df698028f0e