Are you sure you want to delete the question?

Leaving a resolved question undeleted may help others!

ROS クラスをincludeするとbuild時にエラーが出る

概要

ROSを最近始めた学生です。
classを用いたファイルをインクルードするとエラーが発生し、buildが通りません。cmakelist側のファイルパスは合っておりメンバ関数でないものでは正常にbuildできます。

解決法をご存知の方がいればどうかお知恵を貸していただけると幸いです。

発生している問題・エラー

Errors     << controller:make /home/name/catkin_ws/logs/controller/build.make.001.log           
/usr/bin/ld: CMakeFiles/param_test.dir/src/main.cpp.o: in function `NodeInfo::wheel_callback()':
/home/name/catkin_ws/src/2023/controller/src/main.cpp:39: undefined reference to `NITNC::Serial::Transmit(unsigned char*, unsigned char)'
collect2: error: ld returned 1 exit status
make[2]: *** [CMakeFiles/param_test.dir/build.make:101: /home/name/catkin_ws/devel/.private/controller/lib/controller/param_test] エラー 1
make[1]: *** [CMakeFiles/Makefile2:150: CMakeFiles/param_test.dir/all] エラー 2
make: *** [Makefile:141: all] エラー 2

該当するソースコード

main.cpp
#include <ros/ros.h>
#include "serial.hpp"

using namespace NITNC;

class NodeInfo
{
    public:
        ros::NodeHandle nh;
        ros::NodeHandle pnh;
        ros::Timer timer;
        Serial MySerial;

        void timer_callback(const ros::TimerEvent& e);
        void wheel_callback();
        void mechanical_callback();

        NodeInfo()
        {
            timer = nh.createTimer( ros::Duration( 0.1 ), &NodeInfo::timer_callback, this );

        }

};

void NodeInfo::wheel_callback()
{
    int x,y = 0;
    uint8_t tx_x,tx_y = 0;
    pnh.getParam("param_test/wheel/x",x);
    pnh.getParam("param_test/wheel/y",y);

    ROS_INFO( "wheel:\nx : %d\ny : %d", x, y);

    tx_x = x+50;
    tx_y = y+50;

    uint8_t tx_data[ 8 ] = { 0, tx_x, tx_y, 0, 0, 0, 0, 0 };
    MySerial.Transmit( tx_data,sizeof( tx_data ) );

}


void NodeInfo::mechanical_callback()
{

}
void NodeInfo::timer_callback(const ros::TimerEvent& e)
{
    wheel_callback();
    mechanical_callback();
}

int main( int argc, char** argv )
{
    ros::init( argc, argv, "main");
    NodeInfo node_info;
    // node_info.MySerial.Init( B38400 );

    ros::spin();
    return 0;
}

インクルードしているファイル

serial.hpp
#pragma once

#include <ros/ros.h>
#include <termios.h>
#include <fcntl.h>
#include <unistd.h>

namespace NITNC
{
    class Serial
    {
        private:

            struct serial_info
            {
                int fd = 0;
                std::string device_name = "/dev/ttyACM0";
            };
            serial_info info;

        public:
        
            void Init( speed_t BAUDRATE );

            void Transmit(uint8_t* pData, uint8_t size );

            bool CheckSerial( int wait_sec );
    };
}
serial.cpp
#include "serial.hpp"
#include <fcntl.h>
#include <termios.h>

namespace NITNC
{

    void Serial::Init( speed_t BAUDRATE )
        {
            info.fd = open(
                info.device_name.c_str() ,
                O_RDWR | O_NOCTTY | O_NONBLOCK 
                );

            if( info.fd <= 0 )
            {
                ROS_ERROR("faild to open device file.");
                bool result = CheckSerial(10);
                if(result)
                {
                    info.fd = open(
                        info.device_name.c_str() ,
                        O_RDWR | O_NOCTTY | O_NONBLOCK 
                        );
                    ROS_INFO("succeced to open device file.");
                }
                else
                {
                    ROS_INFO("shutdown...");
                    ros::shutdown();
                }
            }
            else
            {
                ROS_INFO("succeced to open device file.");
            }

            struct termios conf_tio;
            //open serial
            fcntl(info.fd, F_SETFL,0);
            tcgetattr(info.fd,&conf_tio);

            // speed_t BAUDRATE = B38400;
            cfsetispeed(&conf_tio, BAUDRATE);
            cfsetospeed(&conf_tio, BAUDRATE);

            conf_tio.c_lflag &= ~(ECHO | ICANON);
            conf_tio.c_cc[VMIN]=0;
            conf_tio.c_cc[VTIME]=0;
            tcsetattr(info.fd,TCSANOW,&conf_tio);
        }

    void Serial::Transmit(uint8_t* pData, uint8_t size )
    {
        {
            std::string data;

            for( int i = 0; i < size; i++ )
            {
                data += pData[ i ];
            }

            int result = write( info.fd, data.c_str(), data.size() );
            if(result >= 1)
            {
                ROS_INFO("succceced to write data.");
            }
            else
            {
                ROS_WARN("faild to write data.");
            }
        }
    }


    bool Serial::CheckSerial( int wait_sec )
    {
        for(int i = 0; i < wait_sec ;i++)
        {
            int check_result = open(
                info.device_name.c_str() ,
                O_RDWR | O_NOCTTY | O_NONBLOCK 
                );

            if( check_result >= 0 )
            {
                return true;
            }
            else
            {
                ROS_WARN("please connect serial port. count : %d",i);
            }
            sleep( 1 );
        }
        ROS_ERROR("faild to connect serial");
        return false;
    }
}

cmakelists

CMakeLists.txt
cmake_minimum_required(VERSION 3.0.2)
project(controller)

## Compile as C++11, supported in ROS Kinetic and newer
# add_compile_options(-std=c++11)

## Find catkin macros and libraries
## if COMPONENTS list like find_package(catkin REQUIRED COMPONENTS xyz)
## is used, also find other catkin packages
find_package(catkin REQUIRED COMPONENTS
  roscpp
  std_msgs
)

## System dependencies are found with CMake's conventions
# find_package(Boost REQUIRED COMPONENTS system)


## Uncomment this if the package has a setup.py. This macro ensures
## modules and global scripts declared therein get installed
## See http://ros.org/doc/api/catkin/html/user_guide/setup_dot_py.html
# catkin_python_setup()

################################################
## Declare ROS messages, services and actions ##
################################################

## To declare and build messages, services or actions from within this
## package, follow these steps:
## * Let MSG_DEP_SET be the set of packages whose message types you use in
##   your messages/services/actions (e.g. std_msgs, actionlib_msgs, ...).
## * In the file package.xml:
##   * add a build_depend tag for "message_generation"
##   * add a build_depend and a exec_depend tag for each package in MSG_DEP_SET
##   * If MSG_DEP_SET isn't empty the following dependency has been pulled in
##     but can be declared for certainty nonetheless:
##     * add a exec_depend tag for "message_runtime"
## * In this file (CMakeLists.txt):
##   * add "message_generation" and every package in MSG_DEP_SET to
##     find_package(catkin REQUIRED COMPONENTS ...)
##   * add "message_runtime" and every package in MSG_DEP_SET to
##     catkin_package(CATKIN_DEPENDS ...)
##   * uncomment the add_*_files sections below as needed
##     and list every .msg/.srv/.action file to be processed
##   * uncomment the generate_messages entry below
##   * add every package in MSG_DEP_SET to generate_messages(DEPENDENCIES ...)

## Generate messages in the 'msg' folder
# add_message_files(
#   FILES
#   Message1.msg
#   Message2.msg
# )

## Generate services in the 'srv' folder
# add_service_files(
#   FILES
#   Service1.srv
#   Service2.srv
# )

## Generate actions in the 'action' folder
# add_action_files(
#   FILES
#   Action1.action
#   Action2.action
# )

## Generate added messages and services with any dependencies listed here
# generate_messages(
#   DEPENDENCIES
#   std_msgs
# )

################################################
## Declare ROS dynamic reconfigure parameters ##
################################################

## To declare and build dynamic reconfigure parameters within this
## package, follow these steps:
## * In the file package.xml:
##   * add a build_depend and a exec_depend tag for "dynamic_reconfigure"
## * In this file (CMakeLists.txt):
##   * add "dynamic_reconfigure" to
##     find_package(catkin REQUIRED COMPONENTS ...)
##   * uncomment the "generate_dynamic_reconfigure_options" section below
##     and list every .cfg file to be processed

## Generate dynamic reconfigure parameters in the 'cfg' folder
# generate_dynamic_reconfigure_options(
#   cfg/DynReconf1.cfg
#   cfg/DynReconf2.cfg
# )

###################################
## catkin specific configuration ##
###################################
## The catkin_package macro generates cmake config files for your package
## Declare things to be passed to dependent projects
## INCLUDE_DIRS: uncomment this if your package contains header files
## LIBRARIES: libraries you create in this project that dependent projects also need
## CATKIN_DEPENDS: catkin_packages dependent projects also need
## DEPENDS: system dependencies of this project that dependent projects also need
catkin_package(
#  INCLUDE_DIRS include
#  LIBRARIES controller
#  CATKIN_DEPENDS roscpp std_msgs
#  DEPENDS system_lib
)

###########
## Build ##
###########

## Specify additional locations of header files
## Your package locations should be listed before other locations
include_directories(
include
  ${catkin_INCLUDE_DIRS}
  ../packages/serial
)

## Declare a C++ library
# add_library(${PROJECT_NAME}
#   src/${PROJECT_NAME}/controller.cpp
# )

## Add cmake target dependencies of the library
## as an example, code may need to be generated before libraries
## either from message generation or dynamic reconfigure
# add_dependencies(${PROJECT_NAME} ${${PROJECT_NAME}_EXPORTED_TARGETS} ${catkin_EXPORTED_TARGETS})

## Declare a C++ executable
## With catkin_make all packages are built within a single CMake context
## The recommended prefix ensures that target names across packages don't collide

file( GLOB SERIAL ../packages/*.cpp)


add_executable(
  param_test
  src/main.cpp
  ${SERIAL}
  )

## Rename C++ executable without prefix
## The above recommended prefix causes long target names, the following renames the
## target back to the shorter version for ease of user use
## e.g. "rosrun someones_pkg node" instead of "rosrun someones_pkg someones_pkg_node"
# set_target_properties(${PROJECT_NAME}_node PROPERTIES OUTPUT_NAME node PREFIX "")

## Add cmake target dependencies of the executable
## same as for the library above
# add_dependencies(${PROJECT_NAME}_node ${${PROJECT_NAME}_EXPORTED_TARGETS} ${catkin_EXPORTED_TARGETS})

## Specify libraries to link a library or executable target against
target_link_libraries(param_test
  ${catkin_LIBRARIES}
  ${SERIAL}
)

set(CMAKE_BUILD_TYPE Debug)

#############
## Install ##
#############

# all install targets should use catkin DESTINATION variables
# See http://ros.org/doc/api/catkin/html/adv_user_guide/variables.html

## Mark executable scripts (Python etc.) for installation
## in contrast to setup.py, you can choose the destination
# catkin_install_python(PROGRAMS
#   scripts/my_python_script
#   DESTINATION ${CATKIN_PACKAGE_BIN_DESTINATION}
# )

## Mark executables for installation
## See http://docs.ros.org/melodic/api/catkin/html/howto/format1/building_executables.html
# install(TARGETS ${PROJECT_NAME}_node
#   RUNTIME DESTINATION ${CATKIN_PACKAGE_BIN_DESTINATION}
# )

## Mark libraries for installation
## See http://docs.ros.org/melodic/api/catkin/html/howto/format1/building_libraries.html
# install(TARGETS ${PROJECT_NAME}
#   ARCHIVE DESTINATION ${CATKIN_PACKAGE_LIB_DESTINATION}
#   LIBRARY DESTINATION ${CATKIN_PACKAGE_LIB_DESTINATION}
#   RUNTIME DESTINATION ${CATKIN_GLOBAL_BIN_DESTINATION}
# )

## Mark cpp header files for installation
# install(DIRECTORY include/${PROJECT_NAME}/
#   DESTINATION ${CATKIN_PACKAGE_INCLUDE_DESTINATION}
#   FILES_MATCHING PATTERN "*.h"
#   PATTERN ".svn" EXCLUDE
# )

## Mark other files for installation (e.g. launch and bag files, etc.)
# install(FILES
#   # myfile1
#   # myfile2
#   DESTINATION ${CATKIN_PACKAGE_SHARE_DESTINATION}
# )

#############
## Testing ##
#############

## Add gtest based cpp test target and link libraries
# catkin_add_gtest(${PROJECT_NAME}-test test/test_controller.cpp)
# if(TARGET ${PROJECT_NAME}-test)
#   target_link_libraries(${PROJECT_NAME}-test ${PROJECT_NAME})
# endif()

## Add folders to be run by python nosetests
# catkin_add_nosetests(test)

ファイル構成

image.png

自分で試したこと

上記の通り、クラスでない関数のみを使用するとエラーは発生しませんでした。

※追記
コンストラクタは正常にビルド通りました。

Serial.cppを載せました。(2023/03/14)

0

1Answer

切り分けのため、呼び出し時のパラメーターをuint8_t*, uint8_tに強引にキャストしてみたらどうなります?

1Like

Comments

  1. imagouさん毎度ご回答ありがとうございます。
    呼び出した関数Transmitの引数を
    MySerial.Transmit( static_cast< uint8_t* >( tx_data ), static_cast<uint8_t>( sizeof( tx_data ) ) );
    のようにキャストしましたが、同じエラーが出ました。
  2. 先程いろいろ試していたところ、cppファイルにて記述していた関数をすべてhppファイルにまとめて書くとビルドが通りました。
    これでなぜ解決したのかわからないのですが、なにか原因をご存知でしょうか。
  3. 凄い単純な問題のような気がしていますが、ちょっと考えてみますね。時間あれば。。。

    追記:
    Serial.cppの書き方が気になります。「NITNC::Serial::Transmit(unsigned char*, unsigned char)」が実装されていない(とみなされている)のではないでしょうか。
  4. ありがとうございます。
    先程Serial.cpp を載せましたので見ていただけるとありがたいです。
  5. MySerial::Initを呼ぶと怒られます?
  6. はい。どの関数でもダメでした。
  7. ならば、直接的な原因は、Serial.oがないか、あってもリンク対象になっていないかのいずれかでしょう。
    要は、Serial.cppがビルド対象となっていないってことです。

    ここまでの情報でじゅうぶんデバッグ?はできるかなと思います。頑張ってください。
  8. ありがとうございます。頑張ってみます。
  9. CMakeLists の file(GLOB SERIAL ....)の部分のパスが間違っていました。助言していただきありがとうございました。
  10. ご自身で解決、素晴らしいですね!きっとこれからの糧になると思います!

Your answer might help someone💌