ROS2 typesupport C
ROS2 のメッセージ定義から生成されるrosidl_typesupport_cの構造体及び関数群をまとめる。基となる定義は以下のものとする。
int32 int_value
Other other_value
Other[] dynamic_array
Other[3] static_array
int32 int_request
---
int32 int_response
int32 order
---
int32[] sequence
---
int32[] sequence
各msg
,srv
,action
はまずidl
ファイルに変換される。msg
などの仕様は About ROS2 interfacesを、idl
の仕様についてはIDL - Interface Definition and Language Mappingを参照。OMG IDL 4.2 specificationのサブセットになっている。ROS2のドキュメントでの参照は7章からになっているが、これはIDLの文法の説明が7章からであるため。文法はEBNFで記述されているため、IDLパーサーを作る際はこれを素直に記述した方が良さそう。
IDL変換 - 基本形
msgなどを変換すると基本的に以下のような構造になる
/// コメント
module my_package { // <- パッケージ名
module msg { // <- msg|srv|action
typedef my_package::msg::Other my_pacakge__msg__Other; // 固定長配列用
typedef my_package__msg__Other my_package__msg__Other__3[3]; 固定長配列用
struct MyMsg {
int32 int_value;
my_package::msg::Other other_value; // 同一パッケージであっても名前空間付き
sequence<my_package::msg::Other> dynamic_array; // 動的配列はテンプレートに
my_package__msg__Other__3 static_array; // ↑ でtypedef されたものが使われる
};
};
};
定数を定義している場合は追加の名前空間が発生する。
module my_package {
module msg {
module MyConstants_Constants {
const boolean BOOL_CONST = TRUE;
};
struct MyConstants {
...
};
};
};
デフォルト値はアノテーションによって付与される。
module my_package {
module msg {
module MyDefaults {
@default (value=TRUE)
boolean bool_value;
};
};
};
モジュールのネストは基本的に2つ(パッケージ名、メッセージの種別)か3つ(+定数)だが、ベンダー依存っぽいパッケージで例外的に多いものがあったはず。ぱっと見つからなかったので見つかったら追記する。
msg -> idl
↑ で説明した通り。1つのIDLファイルに1つの構造体がある。
srv -> idl
1つのIDLファイルに以下のような2つの構造体が作られる。
module my_package {
module srv {
struct MySrv_Request {
int32 int_request;
};
struct MySrv_Response {
int32 int_response;
};
};
};
action -> idl
1つのIDLファイルに以下のような3つの構造体が作られる。
module my_package {
module action {
struct Fibonacci_Goal {
int32 order;
};
struct Fibonacci_Result {
sequence<int32> sequence;
};
struct Fibonacci_Feedback {
sequence<int32> sequence;
};
};
};
msg
とsrv
、action
の識別はIDLファイルに含まれるstruct
の数で行っている。名前空間ではないことに注意。msg
などではなく、直接IDLファイルを定義する際もこのルールに従う。
以降はCヘッダー生成の話。
全般
C言語として見た場合の構成要素は以下の通り。
項目 | 定義 | コメント |
---|---|---|
構造体 |
idl__struct.h.em msg__struct.h.em |
共通(srvなら2つ構造体を作る) |
typesupport |
idl__type_support_h.em : 共通 msg__type_support.h.em : msg srv__type_support.h.em : srv action__type_support.h.em : action |
|
関数 |
idl__functions.h.em msg__functions.h.em |
共通 構造体の生成と破棄に関わる関数群でsrvやactionとしての振舞いは含まれない |
構造体
IDLにおける各構造体(+α)に対してそれぞれ
- 定数の定義(定数は構造体に紐付くのでsrvのように2つある場合は2箇所に表れる)
- include の記述(依存している定義に基づく。重複した場合はコメントアウト(雑では…)
- 構造体を定義
- 動的配列用の構造体を定義
を行う。actionの場合、IDLに記述されている(goal,result,feedback)ものだけではなく、actionのプロトコル用に増える。
rosidl | idl | c |
---|---|---|
msg | MyMsg | MyMsg |
srv | MySrv_Request MySrv_Response |
MySrv_Request MySrv_Response |
action | MyAction_Goal MyAction_Result MyAction_Feedback |
MyAction_Goal MyAction_Result MyAction_Feedback MyAction_SendGoal_Request MyAction_SendGoal_Response MyAction_GetResult_Request MyAction_GetResult_Response MyAction_FeedbackMessage |
ここでは省略しているが、構造体は名前空間に基づいて修飾される。これは、[*namespaces, struct].join('__')
みたいな感じになる。典型的には<pkg>__<type>__<struct>
Actionの関係性。Actionは、実態としてはServiceとMessageの組み合わせで実現されている。そのための情報が付与される。
配列用の構造体は以下のような定義になる。
関数
構造体毎に関数を作る。msg,srv,actionに違いはない。配列用と配列でないもの用(構造体用とする)とで組み合わせに違いはないが、引数が若干違う。
構造体用
bool MyMsg__init(MyMsg * msg)
void MyMsg__fini(MyMsg * msg)
MyMsg* MyMsg__create()
void MyMsg__destroy(MyMsg * msg)
bool MyMsg__are_equal(const MyMsg * lhs, const MyMsg * rhs)
bool MyMsg__copy(const MyMsg * input, MyMsg * output)
配列用
bool MyMsg__Sequence__init(MyMsg__Sequence * array, size_t size)
void MyMsg__Sequence__fini(MyMsg__Seqnence * array)
MyMsg__Sequence * MyMsg__Sequence__create(size_t size)
void MyMsg__Sequence__destroy(MyMsg__Sequence * array)
bool MyMsg__Sequence__are_equal(const MyMsg__Sequence * hls, const MyMsg__Sequence * rhs)
bool MyMsg__Sequence__copy(const MyMsg__Sequence * input, MyMsg__Seqnence * output)
なお、are_equal
とcopy
は 2022年1月以降に増えたもので、Foxyにもバックポートされている。
Typesupport
Typesupportはmsg,srv,actionで若干変化がある。
msg
const rosidl_message_type_support_t *
ROSIDL_TYPESUPPORT_INTERFACE__MESSAGE_SYMBOL_NAME(
rosidl_typesupport_c,
@(',\n '.join(message.structure.namespaced_type.namespaced_name()))
)();
ROSIDL_TYPESUPPORT_INTERFACE__MESSAGE_SYMBOL_NAME
はマクロ関数、D関数と考えると以下のようになる。
string ROSIDL_TYPESUPPORT_INTERFACE__MESSAGE_SYMBOL_NAME(
string typesupport_name,
string package_name,
string interface_type,
string message_name
) {
return [
typesupport_name,
"get_message_type_support_handle",
package_name,
interface_type,
message_name
].join("__");
}
srv
RequestとResponse構造体に対して msg
と同じTypesupport関数、それからsrv向けの関数が追加される。
const rosidl_service_type_support_t *
ROSIDL_TYPESUPPORT_INTERFACE__SERVICE_SYMBOL_NAME(
rosidl_typesupport_c,
@(',\n '.join(service.namespaced_type.namespaced_name()))
)();
mypkg::srv::MySrv
があった時、
-
mypkg::srv::MySrv__Request
-> msg用 -
mypkg::srv::MySrv__Response
-> msg用 -
mypkg::srv::MySrv
-> srv用
となる。
ROSIDL_TYPESUPPORT_INTERFACE__SERVICE_SYMBOL_NAME
もまたマクロ関数で、D関数で考えると以下のようになる。
string ROSIDL_TYPESUPPORT_INTERFACE__SERVICE_SYMBOL_NAME (
string typesupport_name,
string package_name,
string interface_type,
string service_name
) {
return [
typesupport_name,
"get_service_type_support_handle",
package_name,
interface_type,
message_name
].join("__");
}
action
actionはmsgとsrvの組み合わせなので、それらのTypesupport と action用の関数が追加される。
const rosidl_action_type_support_t *
ROSIDL_TYPESUPPORT_INTERFACE__ACTION_SYMBOL_NAME(
rosidl_typesupport_c,
@(',\n '.join(action.namespaced_type.namespaced_name()))
)();
mypkg::action::MyAction
があった時、
-
mypkg::action::MyAction_Goal
-> msg用 -
mypkg::action::MyAction_Result
-> msg用 -
mypkg::action::MyAction_Feedback
-> msg用 -
mypkg::action::MyAction_SendGoal
-> srv用 -
mypkg::action::MyAction_GetResult
-> srv用 -
mypkg::action::MyAction_FeedbackMessage
-> msg用 -
mypkg::action::MyAction
-> action用
となる。
ROSIDL_TYPESUPPORT_INTERFACE__ACTION_SYMBOL_NAME
もまたマクロ関数で、D関数で考えると以下のようになる。
string ROSIDL_TYPESUPPORT_INTERFACE__SERVICE_SYMBOL_NAME (
string typesupport_name,
string package_name,
string interface_type,
string service_name
) {
return [
typesupport_name,
"get_action_type_support_handle",
package_name,
interface_type,
message_name
].join("__");
}