跳到正文

Wiki

四大通訊

約 100 分鐘閱讀

本文由簡體中文內容確定性轉換,並受版本化術語表保護。

1.通訊機制簡介與程式碼模板

#include "rclcpp/rclcpp.hpp"

class MyNode: public rclcpp::Node
{
  public:
    MyNode():Node("mynode_node_cpp")
    {
      RCLCPP_INFO(this->get_logger(),"Hello World!");
    }
};

int main(int argc, char ** argv)
{
  rclcpp::init(argc,argv);

  rclcpp::spin(std::make_shared<MyNode>());

  rclcpp::shutdown();
  return 0;
}

https://snippet-generator.app/

{
"ROS2节点模板(C++)": {
    "prefix": "ros2_node_cpp",
    "body": [
      "#include \"rclcpp/rclcpp.hpp\"",
      "",
      "class MyNode: public rclcpp::Node",
      "{",
      "  public:",
      "    MyNode():Node(\"mynode_node_cpp\")",
      "    {",
      "      RCLCPP_INFO(this->get_logger(),\"Hello World!\");",
      "    }",
      "};",
      "",
      "int main(int argc, char ** argv)",
      "{",
      "  rclcpp::init(argc,argv);",
      "",
      "  rclcpp::spin(std::make_shared<MyNode>());",
      "",
      "  rclcpp::shutdown();",
      "  return 0;",
      "}"
    ],
    "description": "ROS2节点模板(C++)"
  }
}


第一個視窗是服務端,第二個視窗是客戶端。

通訊至少要涉及兩方,只是一個人算不上通訊。

面向介面,話題是一致的,資料載體也是一致的,就可以無縫對接

話題通訊:只能單向傳輸資料

服務通訊:雙向通訊,可以互為客戶端和服務端,客戶端給服務端發資料,服務端給客戶端響應

動作通訊:和服務通訊很像,有服務端給客戶端發的最終響應,但是中間也會連續傳送反饋給客戶端。

引數服務:引數服務是基於服務通訊的,引數客戶先發送一個請求,然後從引數服務的資料池裡拿走資料。也可以更改資料池裡的東西,但是不能刪除。

引數通訊不用自己定義介面檔案,系統會自己弄介面檔案,但是開發者是看不到該檔案的,該檔案被封裝了。

我們操作的資料被封裝成引數物件了。


ros2 pkg create + 功能包名(可以寫在前面或者) + --build-type(構建型別) + ament_cmake / ament_python + --dependencies(依賴) + rclcpp(ROS2的CPP客戶端) + --node-name(節點名) + 節點名

在src裡建立功能包

2.話題通訊_理論

在ROS1裡,node和node之間通訊需要經過master,每個傳輸資料的node都需要在master裡註冊相關資料,master再將資訊進行匹配。

一個publisher釋出資料,兩個subscriber都會接收到資料。

3.話題通訊_實驗1(C++)

ros2 pkg create cpp01_topic --build-type ament_cmake --dependencies rclcpp std_msgs base_interfaces_demo

依賴還需要std_msgs,base_interfaces_demo(這裡面存放了我們自己定義的所需的介面)

ros2 pkg create + 功能包名(可以寫在前面或者最後面) + --build-type(構建型別) + ament_cmake / ament_python + --dependencies(依賴) + rclcpp(ROS2的CPP客戶端) + --node-name(節點名) + 節點名



釋出方

定時器是用來控制傳送頻率的,定時器裡還會執行一個回撥函式timer_callback。

count_是計數器,每執行一次這個回撥函式,count_就++;

spin函式是,程式一旦執行到這裡,就返回到上面,返回到上面是為了呼叫回撥函式,如果沒有這個spin函式,那麼我們這個回撥函式是不會被執行的。

以後只要我們建立完節點類物件指標,就要把該指標傳入spin()函數里。


#include "rclcpp/rclcpp.hpp"

class Talker: public rclcpp::Node
{
  public:
    Talker():Node("talker_node_cpp")
    {
      RCLCPP_INFO(this->get_logger(),"发布节点创建!");
    }
};

int main(int argc, char ** argv)
{
  rclcpp::init(argc,argv);

  rclcpp::spin(std::make_shared<Talker>());

  rclcpp::shutdown();
  return 0;
}

此時程式處於掛起狀態,會一直執行,因為spin函式。

想結束得按Ctrl+C。

我們想要的型別在std_msgs裡,所以要加標頭檔案。

create_publisher()第一個入口引數是話題名稱,是一個字串

create_publisher()第二個入口引數是QOS服務質量有關的,是佇列深度是一串數字,暫時可以先填10或者20等。

也就是當網路質量不好的時候,訊息發不出去了,我們可以將資料先存到佇列裡,假設填10,最多就可以存10個數,當網路恢復了,我們就從佇列裡取資料,將其發出。

其他入口引數有預設值,可以暫時先不管。

返回值是一個publisher的指標。

建立定時器,這個函式有模板,但是模板有預設值可以不設定,

然後三個入口引數,

第一個入口引數是持續時間,也就是週期;

第二個入口引數是回撥函式;

第三個入口引數有預設值,先不管。

使用該名稱空間的優勢是,在第一個入口引數,可以直接填時間+單位。

如果是1s就寫1s,是100ms就填100ms。

該函式還有個返回值,返回值是定時器相關的一個指標。

該函式有多個過載,選擇適合自己的一個函式。

釋出物件得先建立物件。

把count轉化成字串併發送。

因為它是一個std::string型別的,我們要轉化成c風格的字串。

儘量在建構函式的時候,給count賦初值0.

#include "rclcpp/rclcpp.hpp"
#include "std_msgs/msg/string.hpp"

using namespace std::chrono_literals;

class Talker: public rclcpp::Node
{
  public:
    Talker():Node("talker_node_cpp"),count(0)
    {
      RCLCPP_INFO(this->get_logger(),"发布节点创建!");
      publisher_ = this->create_publisher<std_msgs::msg::String>("chatter",10);
      timer_ = this->create_wall_timer(1s,std::bind(&Talker::on_timer,this));
    }
  private:
    void on_timer()
    {
      auto message = std_msgs::msg::String();
      message.data = "hello world!" + std::to_string(count++);
      RCLCPP_INFO(this->get_logger(),"发布方发布的消息:%s",message.data.c_str());
      publisher_->publish(message);
    }
    rclcpp::Publisher<std_msgs::msg::String>::SharedPtr publisher_;
    rclcpp::TimerBase::SharedPtr timer_;
    size_t count;
};

int main(int argc, char ** argv)
{
  rclcpp::init(argc,argv);

  rclcpp::spin(std::make_shared<Talker>());

  rclcpp::shutdown();
  return 0;
}

但是不一定訊息真的釋出出去了。

驗證方法:

ros2 topic echo /xxx

這樣才能確定訊息被髮到chatter話題上了。


#include "rclcpp/rclcpp.hpp"

class Listener: public rclcpp::Node
{
public:
    Listener():Node("listener_node_cpp")
    {
        RCLCPP_INFO(this->get_logger(),"订阅方创建!");
    }
};

int main(int argc, char * argv[])
{
    rclcpp::init(argc,argv);

    rclcpp::spin(std::make_shared<Listener>());

    rclcpp::shutdown();
    return 0;
}

編譯之前別忘了編輯配置檔案

依賴包已經自動生成了,不用管。

主要改這三大部分

一共有5個入口引數,後面兩個入口引數有預設值。

第一個入口引數是話題名稱,要保證和釋出方一致;

第二個入口引數是QoS,就是服務質量管理,佇列深度,10或者20暫時隨便設定,可以看看釋出方那邊的QoS的解釋;

第三個入口引數是回撥函式,一旦接收到資料,就觸發該回調函式。

返回值是一個訂閱物件的指標。

std::placeholders::_1這個是佔位符,_1是指一個。這個地方本應該填入訊息。

#include "rclcpp/rclcpp.hpp"
#include "std_msgs/msg/string.hpp"

class Listener: public rclcpp::Node
{
public:
    Listener():Node("listener_node_cpp")
    {
        RCLCPP_INFO(this->get_logger(),"订阅方创建!");
        subscription_ = this->create_subscription<std_msgs::msg::String>("chatter",10,std::bind(&Listener::do_callback,this,std::placeholders::_1));

    }
private:
    void do_callback(const std_msgs::msg::String &msg)
    {
        RCLCPP_INFO(this->get_logger(),"订阅到的消息是:%s",msg.data.c_str());
    }
    rclcpp::Subscription<std_msgs::msg::String>::SharedPtr subscription_;
};

int main(int argc, char * argv[])
{
    rclcpp::init(argc,argv);

    rclcpp::spin(std::make_shared<Listener>());

    rclcpp::shutdown();
    return 0;
}

4.話題通訊_自定義介面資訊

構建依賴

執行依賴

當前功能包所屬的功能包組

find_package是要把構建依賴傳遞過來

然後還要指定當前被構建的介面檔案的路徑(通過這個設定,就可以把.msg轉化成對應的C++和Python程式碼了)

檔名可以自定義,但是首字母必須大寫

寫完之後test_depend報錯了

刪掉即可

編譯依賴是rosidl開頭的,我們通過grep查詢一下

ros2 pkg list | grep -i rosidl

我們用的是這一個,直接複製過來

在list裡這個所屬的功能包組就沒有了,需要自己寫rosidl_interface_packages

這個依賴要和構建依賴一樣

然後我們要為介面檔案生成原始碼

需要使用rosidl_generate_interfaces函式

colcon build --packages-select base_interfaces_demo

會在install空間下生成student.hpp程式碼

以上.msg生成C++的

然後這個是.msg生成的Python的原始碼

也可以通過這個方式來檢測是否編譯正常

interface是介面

5.話題通訊_實驗2(C++)

新建完原始檔之後,要配置CMakeLists

將最基本的框架直接複製過來

然後替換類的名稱

#include "rclcpp/rclcpp.hpp"

class TalkerStu: public rclcpp::Node
{
  public:
    TalkerStu():Node("talkerstu_node_cpp")
    {
      RCLCPP_INFO(this->get_logger(),"发布节点创建!");
    }
};

int main(int argc, char ** argv)
{
  rclcpp::init(argc,argv);

  rclcpp::spin(std::make_shared<TalkerStu>());

  rclcpp::shutdown();
  return 0;
}

為了編碼規範,把talkerstu_node_cpp改成小寫

同樣的,訂閱方也是需要這樣操作

#include "rclcpp/rclcpp.hpp"

class ListenerStu: public rclcpp::Node
{
public:
    ListenerStu():Node("listenerstu_node_cpp")
    {
        RCLCPP_INFO(this->get_logger(),"订阅方创建!");
    }
};

int main(int argc, char * argv[])
{
    rclcpp::init(argc,argv);

    rclcpp::spin(std::make_shared<ListenerStu>());

    rclcpp::shutdown();
    return 0;
}

{
  "configurations": [
    {
      "browse": {
        "databaseFilename": "${default}",
        "limitSymbolsToIncludedHeaders": false
      },
      "includePath": [
        "/opt/ros/humble/include/**",
        "/home/tungchiahui/mysource/ros2src/3.ws01_plumbing/src/base_interfaces_demo/include/**",
        "/usr/include/**",
        "${workspaceFolder}/",
**        "${workspaceFolder}/install/base_interfaces_demo/include/**"
      ],
      "name": "ROS",
      "intelliSenseMode": "gcc-x64",
      "compilerPath": "/usr/bin/gcc",
      "cStandard": "gnu11",
      "cppStandard": "c++14"
    }
  ],
  "version": 4
}


不要忘記字串轉成C風格的。

#include "rclcpp/rclcpp.hpp"
#include "base_interfaces_demo/msg/student.hpp"

using namespace std::chrono_literals;

class TalkerStu: public rclcpp::Node
{
public:
    TalkerStu():Node("talkerstu_node_cpp"),age(0)
    {
      RCLCPP_INFO(this->get_logger(),"发布节点创建!");
      publisher_ = this->create_publisher<base_interfaces_demo::msg::Student>("chatter_stu",10);
      timer_ = this->create_wall_timer(500ms,std::bind(&TalkerStu::on_timer_callback,this));
    }
private:
    void on_timer_callback()
    {
        auto stu = base_interfaces_demo::msg::Student();
        stu.name = "葫芦娃";
        stu.age = age;
        stu.height = 2.20f;
        age++;
        publisher_->publish(stu);
        RCLCPP_INFO(this->get_logger(),"发布的消息:(%s,%d,%.2f)",stu.name.c_str(),stu.age,stu.height);
    }
    rclcpp::Publisher<base_interfaces_demo::msg::Student>::SharedPtr publisher_;
    rclcpp::TimerBase::SharedPtr timer_;
    int32_t age;
};

int main(int argc, char ** argv)
{
  rclcpp::init(argc,argv);

  rclcpp::spin(std::make_shared<TalkerStu>());

  rclcpp::shutdown();
  return 0;
}

雖然釋出方可以列印日誌,但是不代表資訊被正常發出去了。

這樣檢驗才是真能確定資料被髮送出去了。


#include "rclcpp/rclcpp.hpp"
#include "base_interfaces_demo/msg/student.hpp"

using base_interfaces_demo::msg::Student;

class ListenerStu: public rclcpp::Node
{
public:
    ListenerStu():Node("listenerstu_node_cpp")
    {
        RCLCPP_INFO(this->get_logger(),"订阅方创建!");
        Subscription_ = this->create_subscription<Student>("chatter_stu",10,std::bind(&ListenerStu::do_callback,this,std::placeholders::1));
    }
private:
    void do_callback(const Student &stu)
    {
        RCLCPP_INFO(this->get_logger(),"订阅的学生信息:name=%s,age=%d,height=%.2f",stu.name.c_str(),stu.age,stu.height);
    }
_    rclcpp::Subscription<Student>::SharedPtr Subscription_;
};

int main(int argc, char * argv[])
{
    rclcpp::init(argc,argv);

    rclcpp::spin(std::make_shared<ListenerStu>());

    rclcpp::shutdown();
    return 0;
}

6.話題通訊_rqt檢視計算圖


圖形化工具RQT

7.服務通訊_理論

只能有一個服務端,可以有多個客戶端,每個客戶端都可以向服務端傳送請求。(當然可以有多個服務端,但是會出很多邏輯問題,這是極其不合理的,禁止使用)

8.服務通訊_實驗1_服務端實現(C++)

先開服務端,然後從客戶端提交兩個整數,到服務端之後,服務端會解析資料,然後求和,並返回給客戶端。

ros2 pkg create cpp02_service --build-type ament_cmake --dependencies rclcpp base_interfaces_demo --node-name demo01_server

如果之前用過demo_interfaces_demo,那麼一般是不用再配置package.xml了。

記得檔名首字母要大寫!

這是另一個驗證方式


#include "rclcpp/rclcpp.hpp"

class AddIntsServer: public rclcpp::Node
{
  public:
    AddIntsServer():Node("add_ints_server_node_cpp")
    {
      RCLCPP_INFO(this->get_logger(),"服务端节点创建!");
    }
};

int main(int argc, char ** argv)
{
  rclcpp::init(argc,argv);

  rclcpp::spin(std::make_shared<AddIntsServer>());

  rclcpp::shutdown();
  return 0;
}

服務端是一直要掛起的

客戶端是執行完畢就結束返回到終端的

所以客戶端不用呼叫spin函式,直接建立物件即可。

#include "rclcpp/rclcpp.hpp"

class AddIntsClient: public rclcpp::Node
{
  public:
    AddIntsClient():Node("add_ints_client_node_cpp")
    {
      RCLCPP_INFO(this->get_logger(),"客户端节点创建!");
    }
};

int main(int argc, char ** argv)
{
  rclcpp::init(argc,argv);

//   rclcpp::spin(std::make_shared<AddIntsClient>());
  auto client = std::make_shared<AddIntsClient>();

  rclcpp::shutdown();
  return 0;
}

然後還要編輯配置檔案

package.xml現在不用修改


有4個入口引數,但是後兩個有預設值,所以我們只用管前2個。

第一個入口引數就是一個話題名稱,字串

第二個入口引數是回撥函式

返回值是一個service型別的智慧指標

#include "rclcpp/rclcpp.hpp"
#include "base_interfaces_demo/srv/add_ints.hpp"

using base_interfaces_demo::srv::AddInts;

class AddIntsServer: public rclcpp::Node
{
  public:
    AddIntsServer():Node("add_ints_server_node_cpp")
    {
      RCLCPP_INFO(this->get_logger(),"服务端节点创建!");
      server_ = this->create_service<base_interfaces_demo::srv::AddInts>("add_ints",std::bind(&AddIntsServer::add_callback,this,std::placeholders::_1,std::placeholders::2));
    }
  private:
    void add_callback(const AddInts::Request::SharedPtr req,AddInts::Response::SharedPtr res)
    {
      res->sum = req->num1 + (*req).num2;
      RCLCPP_INFO(this->get_logger(),"%d + %d = %d",req->num1,req->num2,res->sum);
    }
_    rclcpp::Service<base_interfaces_demo::srv::AddInts>::SharedPtr server_;
};

int main(int argc, char ** argv)
{
  rclcpp::init(argc,argv);

  rclcpp::spin(std::make_shared<AddIntsServer>());

  rclcpp::shutdown();
  return 0;
}

因為我們的客戶端還沒寫,所以先暫時用ros2 service call這個小工具來檢視服務端的情況

ros2 service call /add_ints base_interfaces_demo/srv/AddInts "{'num1': 10,'num2': 30}"

ros2 service call + 話題名 + 介面資料型別 + json程式碼(也可以理解成yaml格式的)

此json程式碼(yaml格式)格式: "{'第一個數的名': 空格 +對應數值,'第二個數的名': 空格 +對應數的數值}"

9.服務通訊_實驗1_客戶端實現(C++)

執行的時候後面跟了兩個整形資料,

所以這個argc應該是等於3的。

argv[]:接收編譯時的返回的argc的引數

argc是命令列總的引數個數

argv[]是argc個引數,其中第0個引數是程式的全名,以後的引數

必須得保證伺服器開著,並且客戶端能夠連線伺服器,如果伺服器沒開,那麼傳送的資料會丟失,但是一般使用服務通訊的都是比較重要的資訊,一定不要丟失了。

客戶端傳送完資料後,會產生一個響應,這裡直接當函式的返回值給返回了。

#include "rclcpp/rclcpp.hpp"

class AddIntsClient: public rclcpp::Node
{
  public:
    AddIntsClient():Node("add_ints_client_node_cpp")
    {
      RCLCPP_INFO(this->get_logger(),"客户端节点创建!");
    }
};

int main(int argc, char ** argv)
{
  rclcpp::init(argc,argv);

//   rclcpp::spin(std::make_shared<AddIntsClient>());
  auto client = std::make_shared<AddIntsClient>();

  rclcpp::shutdown();
  return 0;
}

這一段就應該放在節點初始化前面,防止多作一些耗資源的操作再進行判斷。

因為RCLCPP_INFO在節點建立的前面,無法使用類和例項化方式進行get_logger,也就是無法使用this指標和節點智慧指標來獲取。

所以我們採用以下方式:

這種方式通過rclcpp裡的get_logger,但是需要給日誌起個名字,放到入口引數裡,我們就叫rclcpp吧。

#include "rclcpp/rclcpp.hpp"

class AddIntsClient: public rclcpp::Node
{
  public:
    AddIntsClient():Node("add_ints_client_node_cpp")
    {
      RCLCPP_INFO(this->get_logger(),"客户端节点创建!");
    }
};

int main(int argc, char ** argv)
{
  if(argc != 3)
  {
    RCLCPP_INFO(rclcpp::get_logger("rclcpp"),"请提交两个整形数字!");
    return 1;
  }
  rclcpp::init(argc,argv);

  auto client = std::make_shared<AddIntsClient>();

  rclcpp::shutdown();
  return 0;
}

如果我不提交引數,直接回車,然後這是一個異常,主函式返回值不是0

也可以把INFO改成ERROR

#include "rclcpp/rclcpp.hpp"

class AddIntsClient: public rclcpp::Node
{
  public:
    AddIntsClient():Node("add_ints_client_node_cpp")
    {
      RCLCPP_INFO(this->get_logger(),"客户端节点创建!");
    }
};

int main(int argc, char ** argv)
{
  if(argc != 3)
  {
    RCLCPP_ERROR(rclcpp::get_logger("rclcpp"),"请提交两个整形数字!");
    return 1;
  }
  rclcpp::init(argc,argv);

  auto client = std::make_shared<AddIntsClient>();

  rclcpp::shutdown();
  return 0;
}

一共3個入口引數,

第一個入口引數是話題名稱,是字串;

第二個入口引數和第三個入口引數有預設值,先不用管;

返回值是客戶端的智慧指標。

一旦Ctrl+C關閉,則會瘋狂爆INFO,且程式無法停止。

再按Ctrl+Z可以停止程式進行。

解決上面Ctrl+C的bug:

rclcpp::ok()這個是判斷當前程式是否正常執行,如果正常執行,則返回true,如果不正常執行則返回false,比如按下Ctrl+C就是不正常執行。

當rclcpp::ok() != true的時候,就是ctrl+c按下了。

這樣直接可以讓函式結束。

這時按下ctrl+c會爆很多錯誤

這是因為

this->get_logger()

client->get_logger()

rclcpp::get_logger()

的不同

這個異常和context有關,初始化的時候會建立context物件,相當於是一個容器,可以往容器裡放資料,也可以在容器裡取資料。

當前,如果我們連線失敗的話,列印日誌。

按下ctrl+c會結束我們的ROS2程式,要釋放資源,比如要關閉context,這時已經關掉了context,這樣,我們再從client和this來獲取日誌,就不行了,所以建議用rclcpp::get_logger()。

因為rclcpp::get_logger()的呼叫和context沒有關係。

這樣程式就正常了!

#include "rclcpp/rclcpp.hpp"
#include "base_interfaces_demo/srv/add_ints.hpp"

using base_interfaces_demo::srv::AddInts;
using namespace std::chrono_literals;

class AddIntsClient: public rclcpp::Node
{
  public:
    AddIntsClient():Node("add_ints_client_node_cpp")
    {
      RCLCPP_INFO(rclcpp::get_logger("rclcpp"),"客户端节点创建!");
      client_ = this->create_client<AddInts>("add_ints");
    }

    bool connect_server()
    {
      while(client_->wait_for_service(1s) != true)
      {
        RCLCPP_INFO(rclcpp::get_logger("rclcpp"),"服务连接中!");

        if (rclcpp::ok() != true)
        {
          RCLCPP_INFO(rclcpp::get_logger("rclcpp"),"强行终止客户端!");
          return false;
        }
      }
      return true;
    }

  private:
    rclcpp::Client<AddInts>::SharedPtr client_;
};

int main(int argc, char ** argv)
{
  if(argc != 3)
  {
    RCLCPP_ERROR(rclcpp::get_logger("rclcpp"),"请提交两个整形数字!");
    return 1;
  }
  rclcpp::init(argc,argv);

  auto client = std::make_shared<AddIntsClient>();

  bool flag = client->connect_server();

  if (flag != true)
  {
    RCLCPP_INFO(rclcpp::get_logger("rclcpp"),"服务器连接失败,程序退出!");
    return 0;
  }

  rclcpp::shutdown();
  return 0;
}


返回值型別有了,我們就貼上過去,

因為using base_interfaces_demo::srv::AddInts所以可以省略成AddInts

在主函數里要呼叫函式。

atoi()是把資料轉化成整形

我們還要處理響應,響應有3個

第一個是中斷,第二個是成功,第三個是超時;

我們一般只判斷成功,其他兩種情況都認為是失敗。

第一個入口引數是 節點的智慧指標

第二個入口引數是future

第三個入口引數有預設值,先不用管

#include "rclcpp/rclcpp.hpp"
#include "base_interfaces_demo/srv/add_ints.hpp"

using base_interfaces_demo::srv::AddInts;
using namespace std::chrono_literals;

class AddIntsClient: public rclcpp::Node
{
  public:
    AddIntsClient():Node("add_ints_client_node_cpp")
    {
      RCLCPP_INFO(rclcpp::get_logger("rclcpp"),"客户端节点创建!");
      client_ = this->create_client<AddInts>("add_ints");
    }

    bool connect_server()
    {
      while(client_->wait_for_service(1s) != true)
      {
        RCLCPP_INFO(rclcpp::get_logger("rclcpp"),"服务连接中!");

        if (rclcpp::ok() != true)
        {
          RCLCPP_INFO(rclcpp::get_logger("rclcpp"),"强行终止客户端!");
          return false;
        }
      }
      return true;
    }

    rclcpp::Client<AddInts>::FutureAndRequestId send_request(int32_t num1,int32_t num2)
    {
      /*
        返回值 rclcpp::Client<base_interfaces_demo::srv::AddInts>::FutureAndRequestId
        入口参数 async_send_request(std::shared_ptr<base_interfaces_demo::srv::AddInts_Request> request)  //其实就相当于AddInts::Request类型
      */
      auto request = std::make_sharedautolinkAddInts::Requestautolink();
      request->num1 = num1;
      request->num2 = num2;
      return client_->async_send_request(request);
    }

  private:
    rclcpp::Client<AddInts>::SharedPtr client_;
};

int main(int argc, char ** argv)
{
  if(argc != 3)
  {
    RCLCPP_ERROR(rclcpp::get_logger("rclcpp"),"请提交两个整形数字!");
    return 1;
  }
  rclcpp::init(argc,argv);

  auto client = std::make_shared<AddIntsClient>();

  bool flag = client->connect_server();

  if (flag != true)
  {
    RCLCPP_INFO(rclcpp::get_logger("rclcpp"),"服务器连接失败,程序退出!");
    return 0;
  }
  auto future = client->send_request(atoi(argv[1]),atoi(argv[2]));

  if (rclcpp::spin_until_future_complete(client,future) == rclcpp::FutureReturnCode::SUCCESS)
  {
    RCLCPP_INFO(client->get_logger(),"响应成功! sum = %d",future.get()->sum);
  }
  else
  {
    RCLCPP_INFO(client->get_logger(),"响应失败!");
  }

  rclcpp::shutdown();
  return 0;
}

10.動作通訊_理論

讓B一直給A返回當前機器人的狀態資訊,這樣的行為通訊更符合我們操控機器人的導航需求。

輸入10,會累加1-10的所有數,並且會遍歷1-10所有的數,並進行累加,累加是需要耗時的,假設每累加一次,耗時一秒,

然後為了好看出來程式執行情況,在每累加的時候,都發一個INFO,代表當前進度。

可以在進行任務時,把任務取消掉。

第一步,客戶端給服務端發目標資料

第二步,服務端評估目標資料,並反饋給客戶端這個評估結果(是否能夠達到目標)

第三步,客戶端再給服務端發最終確定的目標資料

第四步,服務端一直反饋給客戶端執行的過程資料

第五步,結束之後,服務端反饋給客戶端最終的結果


ros2 pkg create cpp03_action --build-type ament_cmake --dependencies rclcpp rclcpp_action base_interfaces_demo --node-name demo01_action_server


最頂上是請求資料,

中間是最終響應結果的資料,

最底下是連續反饋的資料。

depend是build depend,exe depend,export depend三者的整合。

ros2 interface show base_interfaces_demo/action/Progress

11.動作通訊_實驗1_服務端實現(C++)

#include "rclcpp/rclcpp.hpp"

class ProgressActionServer: public rclcpp::Node
{
  public:
    ProgressActionServer():Node("progress_action_server_node_cpp")
    {
      RCLCPP_INFO(this->get_logger(),"action服务端创建!");
    }
};

int main(int argc, char ** argv)
{
  rclcpp::init(argc,argv);

  rclcpp::spin(std::make_shared<ProgressActionServer>());

  rclcpp::shutdown();
  return 0;
}

#include "rclcpp/rclcpp.hpp"

class ProgressActionClient: public rclcpp::Node
{
  public:
    ProgressActionClient():Node("progress_action_server_node_cpp")
    {
      RCLCPP_INFO(this->get_logger(),"action客户端创建!");
    }
};

int main(int argc, char ** argv)
{
  rclcpp::init(argc,argv);

  rclcpp::spin(std::make_shared<ProgressActionClient>());

  rclcpp::shutdown();
  return 0;
}


有倆模板,我們只需要設定action就行了,就是我們的動作介面型別。

第一個引數是node,在class裡就用this指標,

第二個引數是話題,字串,

第三個引數是回撥函式用來處理目標值的,

第四個引數是回撥函式用來處理取消請求的,

第五個引數是接收目標值之後,該回調函式生成連續反饋,

第六、第七個引數有預設值,先不管,

返回值是action智慧指標。

Goal_callback解析:

第一個引數是GoalUUID,

第二個引數是我們動作介面下的Goal,

返回值是goalresponse,用的名稱空間是rclcpp_action,底下封裝了3個常量,

第一個是接收並馬上執行,

第二個是接收並推遲執行,

第三個是拒絕。

報錯原因是沒加佔位符

#include "rclcpp/rclcpp.hpp"
#include "rclcpp_action/rclcpp_action.hpp"
#include "base_interfaces_demo/action/progress.hpp"

using base_interfaces_demo::action::Progress;
using std::placeholders::_1;
using std::placeholders::_2;

class ProgressActionServer: public rclcpp::Node
{
  public:
    ProgressActionServer():Node("progress_action_server_node_cpp")
    {
      RCLCPP_INFO(this->get_logger(),"action服务端创建!");
      /*
      rclcpp_action::Server<ActionT>::SharedPtr create_server<ActionT,
      NodeT>(NodeT node,
      const std::string &name,
      rclcpp_action::Server<ActionT>::GoalCallback handle_goal,
      rclcpp_action::Server<ActionT>::CancelCallback handle_cancel,
      rclcpp_action::Server<ActionT>::AcceptedCallback handle_accepted,
      const rcl_action_server_options_t &options = rcl_action_server_get_default_options(),
      rclcpp::CallbackGroup::SharedPtr group = nullptr)
      */
      server_ = rclcpp_action::create_server<Progress>(
        this,
        "get_sum_topic",
        std::bind(&ProgressActionServer::handle_goal_callback,this,_1,_2),
        std::bind(&ProgressActionServer::handle_cancel_callback,this,_1),
        std::bind(&ProgressActionServer::handle_accepted_callback,this,_1)
        );
    }
    //std::function<GoalResponse(const GoalUUID &, std::shared_ptr<const typename ActionT::Goal>)>
    rclcpp_action::GoalResponse handle_goal_callback(const rclcpp_action::GoalUUID &, std::shared_ptr<const Progress::Goal>)
    {

      return rclcpp_action::GoalResponse::ACCEPT_AND_EXECUTE;
    }

    //std::function<CancelResponse(std::shared_ptr<ServerGoalHandle<ActionT>>)>
    rclcpp_action::CancelResponse handle_cancel_callback(std::shared_ptr<rclcpp_action::ServerGoalHandle<Progress>> goal_handle)
    {

      return rclcpp_action::CancelResponse::ACCEPT;
    }

    //std::function<void (std::shared_ptr<ServerGoalHandle<ActionT>>)>
    void handle_accepted_callback(std::shared_ptr<rclcpp_action::ServerGoalHandle<Progress>> goal_handle)
    {
    }

  private:
    rclcpp_action::Server<Progress>::SharedPtr server_;
};

int main(int argc, char ** argv)
{
  rclcpp::init(argc,argv);

  rclcpp::spin(std::make_shared<ProgressActionServer>());

  rclcpp::shutdown();
  return 0;
}

ros2 action send_goal /get_sum_topic base_interfaces_demo/action/Progress -f "{'num': 10}"

ros2 action send_goal /話題名稱 + 介面型別 + -f + 引數

-f是連續反饋,就是可以獲取連續反饋。

傳送目標值為10

然後為我們客戶端設定了一個ID,因為可能有多個客戶端都訪問這個服務端,所以我們要給每個客戶端都設定一個唯一的ID

然後結果是0

因為我們程式暫時啥都還沒寫。


uuid就是客戶端ID,在此時沒有使用,那就用(void)uuid,因為如果不用,編譯器可能報警告。


我們的這個任務是可以正常被取消的,所以直接return accept就可以,根據不同任務需求來在函數里做不同的事。


因為我們的連續反饋和最終響應的生成是耗時操作,為了避免主邏輯出現阻塞,建議單獨再開一個執行緒。

生成連續反饋的API,需要傳參,傳入的引數就是Progress裡的Feedback物件。

get_goal()可以獲取目標值

因為這是個耗時操作,為了看出效果來,所以咱們每次迴圈的時候都給設定一下休眠。

1.0是指1Hz,也就是每隔1秒執行一次。

因為1Hz,所以我們這個rate.sleep()每次都會休眠1秒鐘;

生成最終結果的API,需要傳參,傳入的引數就是Progress裡的Result物件。

ok()函式的返回值:如果正常執行,則返回true,如果不正常執行則返回false


bug:當我們終止客戶端之後,服務端沒有停止執行。服務端要一直執行到程式結束。

bug解決思路:

接收到取消請求後,就是中斷我的主邏輯,也就是execute_callback被關閉,

這個函式返回值是布林值,如果接收到了取消請求就返回true,否則返回false,

我們取消之後,其實仍然可以向客戶端反應最終的結果,用canceled函式,

要把定義result放在前面。

#include "rclcpp/rclcpp.hpp"
#include "rclcpp_action/rclcpp_action.hpp"
#include "base_interfaces_demo/action/progress.hpp"

using base_interfaces_demo::action::Progress;
using std::placeholders::_1;
using std::placeholders::_2;

class ProgressActionServer: public rclcpp::Node
{
  public:
    ProgressActionServer():Node("progress_action_server_node_cpp")
    {
      RCLCPP_INFO(this->get_logger(),"action服务端创建!");
      /*
      rclcpp_action::Server<ActionT>::SharedPtr create_server<ActionT,
      NodeT>(NodeT node,
      const std::string &name,
      rclcpp_action::Server<ActionT>::GoalCallback handle_goal,
      rclcpp_action::Server<ActionT>::CancelCallback handle_cancel,
      rclcpp_action::Server<ActionT>::AcceptedCallback handle_accepted,
      const rcl_action_server_options_t &options = rcl_action_server_get_default_options(),
      rclcpp::CallbackGroup::SharedPtr group = nullptr)
      */
      server_ = rclcpp_action::create_server<Progress>(
        this,
        "get_sum_topic",
        std::bind(&ProgressActionServer::handle_goal_callback,this,_1,_2),
        std::bind(&ProgressActionServer::handle_cancel_callback,this,_1),
        std::bind(&ProgressActionServer::handle_accepted_callback,this,_1)
        );
    }

    //std::function<GoalResponse(const GoalUUID &, std::shared_ptr<const typename ActionT::Goal>)>
    rclcpp_action::GoalResponse handle_goal_callback(const rclcpp_action::GoalUUID &uuid, std::shared_ptr<const Progress::Goal> goal)
    {
      (void)uuid;
      if(goal->num <= 1)
      {
        RCLCPP_INFO(this->get_logger(),"提交的目标值必须大于1!");
        return rclcpp_action::GoalResponse::REJECT;
      }
      RCLCPP_INFO(this->get_logger(),"提交的目标值合法!");
      return rclcpp_action::GoalResponse::ACCEPT_AND_EXECUTE;
    }

    //std::function<CancelResponse(std::shared_ptr<ServerGoalHandle<ActionT>>)>
    rclcpp_action::CancelResponse handle_cancel_callback(std::shared_ptr<rclcpp_action::ServerGoalHandle<Progress>> goal_handle)
    {
      (void)goal_handle;
      RCLCPP_INFO(this->get_logger(),"接收到任务取消请求!");
      return rclcpp_action::CancelResponse::ACCEPT;
    }

    //std::function<void (std::shared_ptr<ServerGoalHandle<ActionT>>)>
    void execute_callback(std::shared_ptr<rclcpp_action::ServerGoalHandle<Progress>> goal_handle)
    {
      //void publish_feedback(std::shared_ptr<base_interfaces_demo::action::Progress_Feedback> feedback_msg)
      //goal_handle->publish_feedback();
      int num = goal_handle->get_goal()->num;
      int sum = 0;
      auto feedback = std::make_sharedautolinkProgress::Feedbackautolink();
      auto result = std::make_sharedautolinkProgress::Resultautolink();
      rclcpp::Rate rate(1.0);
      for (int32_t i = 1; i <= num; i++)
      {
        sum += i;
        double progress = i / (double)num;
        feedback->progress = progress;

        goal_handle->publish_feedback(feedback);
        RCLCPP_INFO(this->get_logger(),"连续反馈中,当前进度为:%.2f",progress);

        if(goal_handle->is_canceling() == true)
        {
          result->sum = sum;
          goal_handle->canceled(result);
          RCLCPP_INFO(this->get_logger(),"任务被取消了!");
          return;
        }

        rate.sleep();
      }

      //void succeed(std::shared_ptr<base_interfaces_demo::action::Progress_Result> result_msg)
      //goal_handle->succeed();

      if(rclcpp::ok() == true)
      {
        result->sum = sum;
        goal_handle->succeed(result);
        RCLCPP_INFO(this->get_logger(),"最终响应结果为:%d",sum);
      }

    }

    void handle_accepted_callback(std::shared_ptr<rclcpp_action::ServerGoalHandle<Progress>> goal_handle)
    {
      std::thread(std::bind(&ProgressActionServer::execute_callback,this,goal_handle)).detach();
    }

  private:
    rclcpp_action::Server<Progress>::SharedPtr server_;
};

int main(int argc, char ** argv)
{
  rclcpp::init(argc,argv);

  rclcpp::spin(std::make_shared<ProgressActionServer>());

  rclcpp::shutdown();
  return 0;
}

12.動作通訊_實驗1_客戶端實現(C++)

這條紅色的線是在action_client幫我們封裝好的,所以可以不用管。

#include "rclcpp/rclcpp.hpp"

class ProgressActionClient: public rclcpp::Node
{
  public:
    ProgressActionClient():Node("progress_action_server_node_cpp")
    {
      RCLCPP_INFO(this->get_logger(),"action客户端创建!");
    }
};

int main(int argc, char ** argv)
{
  if(argc != 2)
  {
    RCLCPP_INFO(rclcpp::get_logger("rclcppp"),"请输入一个整形数字!");
    return 1;
  }
  rclcpp::init(argc,argv);

  rclcpp::spin(std::make_shared<ProgressActionClient>());

  rclcpp::shutdown();
  return 0;
}

第一個入口引數是node

第二個入口引數是話題名稱,字串

第三個入口引數和第四個入口引數都有預設值

返回值是客戶端智慧指標。

需要把一步分成兩步。

再把send_goal函式呼叫一下。


async_send_goal是非同步傳送目標值

第一個入口引數是我們介面檔案裡的目標值

第二個入口引數是傳送目標選項物件,我們可以設定這個目標傳送過去之後,我們需要處理的一些回撥函式

返回值是

先不管其他的,先把函式隨便定義上,什麼返回值,入口引數都是void,先不讓程式報錯。

得知,

GoalResponseCallback返回值是void,入口引數是goalhandle,goalhandle在本圖的上面,是clientgoalhandle,然後這個clientgoalhandle屬於rclcpp_action工作空間

返回值是void

第一個入口引數是clientgoalhandle

第二個入口引數是feedback


傳送一個數值給服務端,服務端要先拿到目標值進行判斷,判斷該目標值是否可以被接收,或者被拒絕,再把處理結果響應給客戶端。

如果說這個目標值可處理,那麼goal_handle裡是有內容的;

如果不可以被處理,那麼goal_handle是一個nullptr空指標。


接下來處理反饋資料:

如果我們只是解析反饋的資料,那麼goal_handle是用不上的。

需要用倆%來轉譯%,如上圖是列印百分比資料的案例。

假設progress_int是50,則會輸出50%。

如果只想列印一個%,那就需要%%來轉譯。

回撥函式是可能會資料丟失的。這是正常現象。


最終響應:

這個result最終結果的狀態是不一定的,有可能任務被取消了,或被終止了,也有可能任務正常運行了。

所以我們要通過狀態碼來判斷狀態。

第一個是被強行終止

第二個是取消

第三個是成功

第四個是未知

#include "rclcpp/rclcpp.hpp"
#include "rclcpp_action/rclcpp_action.hpp"
#include "base_interfaces_demo/action/progress.hpp"

using base_interfaces_demo::action::Progress;
using namespace std::chrono_literals;
using std::placeholders::_1;
using std::placeholders::_2;

class ProgressActionClient: public rclcpp::Node
{
  public:
    ProgressActionClient():Node("progress_action_server_node_cpp")
    {
      RCLCPP_INFO(this->get_logger(),"action客户端创建!");
      /*
      rclcpp_action::Client<ActionT>::SharedPtr create_client<ActionT,
      NodeT>(NodeT node, const std::string &name,
      rclcpp::CallbackGroup::SharedPtr group = nullptr,
      const rcl_action_client_options_t &options = rcl_action_client_get_default_options())
      */
      client_ = rclcpp_action::create_client<Progress>(this,"get_sum_topic");
    }

    void send_goal(int32_t num)
    {
      if(client_->wait_for_action_server(1s) != true)
      {
        RCLCPP_ERROR(this->get_logger(),"服务连接失败!");
        return;
      }

      /*
      std::shared_future<rclcpp_action::ClientGoalHandle<base_interfaces_demo::action::Progress>::SharedPtr>
      async_send_goal(const base_interfaces_demo::action::Progress::Goal &goal,
      const rclcpp_action::Client<base_interfaces_demo::action::Progress>::SendGoalOptions &options)
      */
      auto goal = Progress::Goal();
      goal.num = num;

      rclcpp_action::Client<Progress>::SendGoalOptions options;
      options.goal_response_callback = std::bind(&ProgressActionClient::goal_response_callback,this,_1);
      options.feedback_callback = std::bind(&ProgressActionClient::feedback_callback,this,_1,_2);
      options.result_callback = std::bind(&ProgressActionClient::result_callback,this,_1);

      auto future = client_->async_send_goal(goal,options);
    }

  void goal_response_callback(rclcpp_action::ClientGoalHandle<Progress>::SharedPtr goal_handle)
  {
    if(goal_handle == nullptr)
    {
      RCLCPP_INFO(this->get_logger(),"目标请求被服务端拒绝!");
    }
    else
    {
      RCLCPP_INFO(this->get_logger(),"目标处理中!");
    }
  }

  void feedback_callback(rclcpp_action::ClientGoalHandle<Progress>::SharedPtr goal_handle,std::shared_ptr<const Progress::Feedback> feedback)
  {
    (void)goal_handle;
    double progress = feedback->progress;
    int progress_int = (int) (progress * 100);
    RCLCPP_INFO(this->get_logger(),"当前进度为:%d%%",progress_int);
  }

  void result_callback(const rclcpp_action::ClientGoalHandle<Progress>::WrappedResult & result)
  {
    if (result.code == rclcpp_action::ResultCode::SUCCEEDED)
    {
      RCLCPP_INFO(this->get_logger(),"最终结果为:%d",result.result->sum);
    }
    else if(result.code == rclcpp_action::ResultCode::ABORTED)
    {
      RCLCPP_INFO(this->get_logger(),"过程被中断!");
    }
    else if(result.code == rclcpp_action::ResultCode::CANCELED)
    {
      RCLCPP_INFO(this->get_logger(),"任务被取消!");
    }
    else
    {
      RCLCPP_INFO(this->get_logger(),"未知异常!");
    }
  }

  private:
    rclcpp_action::Client<Progress>::SharedPtr client_;
};

int main(int argc, char ** argv)
{
  if(argc != 2)
  {
    RCLCPP_ERROR(rclcpp::get_logger("rclcppp"),"请输入一个整形数字!");
    return 1;
  }
  rclcpp::init(argc,argv);

  auto node = std::make_shared<ProgressActionClient>();

  node->send_goal(atoi(argv[1]));

  rclcpp::spin(node);

  rclcpp::shutdown();
  return 0;
}

此時我們取消客戶端,反而服務端還在執行,這裡的ctrl+c只是結束了我們的客戶端,而不是指揮我們的客戶端去下發取消請求指令,我們只有去捕獲一下我們的鍵盤才能完成取消請求指令的傳送。


修復bug:

還沒修復好

// 发送取消请求auto future_cancel = client_->async_cancel_goal(goal_handle);
rclcpp::spin_until_future_complete(this->get_node_base_interface(), future_cancel);
if (future_cancel.wait_for(1s) == std::future_status::ready)
{
  RCLCPP_INFO(this->get_logger(), "终止请求已发送!");
}
else
{
  RCLCPP_ERROR(this->get_logger(), "无法发送终止请求...");
}

13.引數服務_理論與API介紹(C++)

當然還有特殊情況,比如把引數設定為私有的。


其他通訊需要自己弄介面檔案,但是引數服務不用自己弄介面檔案,ROS2已經封裝好了API,所以我們只需要呼叫API即可。

只是想展示一下API,所以先建立引數功能包展示下API,先不建立客戶端和服務端。

rclcpp::parameter 物件(鍵,值);

該函式是給parameter的值賦值的,有18個過載,各種型別。

其中空是說不給賦值,這樣只有鍵,沒有值。

14.引數服務_實驗1_服務端(C++)

這裡有差異,在Node裡傳入了第二個引數,這句是專門用來允許刪除引數的。undeclared解除宣告。

只有查詢和修改的介面API,並沒有新增和刪除的API,這是ROS2根據安全考慮的。

#include "rclcpp/rclcpp.hpp"

class ParamServer: public rclcpp::Node
{
  public:
    ParamServer():Node("param_server_node_cpp")
    {
      RCLCPP_INFO(this->get_logger(),"参数服务端搭建!");
    }
};

int main(int argc, char ** argv)
{
  rclcpp::init(argc,argv);

  auto node = std::make_shared<ParamServer>();

  rclcpp::spin(node);

  rclcpp::shutdown();
  return 0;
}
#include "rclcpp/rclcpp.hpp"

class ParamClient: public rclcpp::Node
{
  public:
    ParamClient():Node("param_client_node_cpp")
    {
      RCLCPP_INFO(this->get_logger(),"参数客户端搭建!");
    }
};

int main(int argc, char ** argv)
{
  rclcpp::init(argc,argv);

  auto node = std::make_shared<ParamClient>();

  rclcpp::spin(node);

  rclcpp::shutdown();
  return 0;
}


鏈式程式設計。

一個普通的節點就可以當引數服務端,不需要另行建立引數服務端。

#include "rclcpp/rclcpp.hpp"

class ParamServer: public rclcpp::Node
{
  public:
    ParamServer():Node("param_server_node_cpp",rclcpp::NodeOptions().allow_undeclared_parameters(true))
    {
      RCLCPP_INFO(this->get_logger(),"参数服务端搭建!");
    }

    void create_param()
    {
      RCLCPP_INFO(this->get_logger(),"-------------增操作--------------");
    }

    void get_param()
    {
      RCLCPP_INFO(this->get_logger(),"-------------查操作--------------");
    }

    void update_param()
    {
      RCLCPP_INFO(this->get_logger(),"-------------改操作--------------");
    }

    void delete_param()
    {
      RCLCPP_INFO(this->get_logger(),"-------------删操作--------------");
    }

};

int main(int argc, char ** argv)
{
  rclcpp::init(argc,argv);

  auto node = std::make_shared<ParamServer>();

  node->create_param();
  node->get_param();
  node->update_param();
  node->delete_param();

  rclcpp::spin(node);

  rclcpp::shutdown();
  return 0;
}

ros2 param list

查詢所有節點裡的所有引數

ros2 param get /節點名稱 引數鍵名 來檢視引數的值


可以通過鍵來查詢引數的值

帶複數形式的函式可以通過由鍵組成的容器來獲取一些引數物件。

來判斷是否有該引數的,入口引數也是鍵,返回值是布林值。


需要傳入parameter物件。

我們覆蓋掉舊值即可。

通過set_parameter也可以建立引數,但是必須宣告allow_undeclared_parameters(true)。


這種宣告的引數不可以被刪除,只能刪除未宣告但設定的。

#include "rclcpp/rclcpp.hpp"

class ParamServer: public rclcpp::Node
{
  public:
    ParamServer():Node("param_server_node_cpp",rclcpp::NodeOptions().allow_undeclared_parameters(true))
    {
      RCLCPP_INFO(this->get_logger(),"参数服务端搭建!");
    }

    void create_param()
    {
      RCLCPP_INFO(this->get_logger(),"-------------增操作--------------");

      this->declare_parameter("car_name","ER");
      this->declare_parameter("width",1.55);
      this->declare_parameter("wheels",5);

      this->set_parameter(rclcpp::Parameter("height",2.00));
    }

    void get_param()
    {
      RCLCPP_INFO(this->get_logger(),"-------------查操作--------------");

      auto car = this->get_parameter("car_name");
      RCLCPP_INFO(this->get_logger(),"key = %s,value = %s",car.get_name().c_str(),car.as_string().c_str()); 

      auto params = this->get_parameters({"car_name","width","wheels"});
      for(auto &¶m : params)
      {
        RCLCPP_INFO(this->get_logger(),"key = %s,value = %s",param.get_name().c_str(),param.value_to_string().c_str());
      }
      bool car_name_flag = this->has_parameter("car_name");
      bool height_flag = this->has_parameter("height");
      RCLCPP_INFO(this->get_logger(),"是否包含car_name? 答案:%d",car_name_flag);
      RCLCPP_INFO(this->get_logger(),"是否包含height? 答案:%d",height_flag);
    }

    void update_param()
    {
      RCLCPP_INFO(this->get_logger(),"-------------改操作--------------");
      this->set_parameter(rclcpp::Parameter("width",1.85));
      RCLCPP_INFO(this->get_logger(),"width = %.2f",this->get_parameter("width").as_double());
    }

    void delete_param()
    {
      RCLCPP_INFO(this->get_logger(),"-------------删操作--------------");
    //   this->undeclare_parameter("car_name");
    //   RCLCPP_INFO(this->get_logger(),"是否包含car_name? 答案:%d",this->has_parameter("car_name"));
      this->undeclare_parameter("height");
      RCLCPP_INFO(this->get_logger(),"是否包含height? 答案:%d",this->has_parameter("height"));
    }

};

int main(int argc, char ** argv)
{
  rclcpp::init(argc,argv);

  auto node = std::make_shared<ParamServer>();

  node->create_param();
  node->get_param();
  node->update_param();
  node->delete_param();

  rclcpp::spin(node);

  rclcpp::shutdown();
  return 0;
}

15.引數服務_實驗1_客戶端(C++)

第一個入口引數是客戶端節點物件,

第二個入口引數是需要連線的服務端的節點名稱。

如果1秒鐘之內連線上了就返回true,如果超時1s沒連線上就返回false。

#include "rclcpp/rclcpp.hpp"

using namespace std::chrono_literals;

class ParamClient: public rclcpp::Node
{
  public:
    ParamClient():Node("param_client_node_cpp")
    {
      RCLCPP_INFO(this->get_logger(),"参数客户端搭建!");
      param_client_ = std::make_sharedautolinkrclcpp::SyncParametersClientautolink(this,"param_server_node_cpp");
    }

    bool connect_server()
    {
      while(param_client_->wait_for_service(1s) != true)
      {
        if(rclcpp::ok() != true)
        {
          return false;
        }
        RCLCPP_INFO(this->get_logger(),"服务连接中!");
      }
      return true;
    }

    void get_param()
    {
      RCLCPP_INFO(this->get_logger(),"-----------参数查询操作-------------");
    }

    void update_param()
    {
      RCLCPP_INFO(this->get_logger(),"-----------参数更新操作-------------");
    }

  private:
    rclcpp::SyncParametersClient::SharedPtr param_client_;
};

int main(int argc, char ** argv)
{
  rclcpp::init(argc,argv);

  auto client = std::make_shared<ParamClient>();

  bool flag = client->connect_server();

  if(!flag)
  {
    return 0;
  }

  client->get_param();
  client->update_param();
  client->get_param();

  // rclcpp::spin(client);

  rclcpp::shutdown();
  return 0;
}

這些話題都是我們此節點名稱下的。


要用高階for

入口引數填引數物件的容器。

我們不僅可以修改值,也可以建立新的引數,但是要保證服務端那邊呼叫過undeclared......