feat: aimrt_py support ros2 protocol interface (#95)

* feat: add checks for ROS 2 message type support

Implement validation functions to ensure message or service types are compliant with ROS 2 standards, enhancing compatibility and error handling when integrating ROS 1 and ROS 2 environments.

* build: update file globbing to include configure dependencies

Ensure source files are automatically detected during configuration by adding CONFIGURE_DEPENDS to the file globbing commands for C++ and Python files. This improves build reliability as it accounts for changes in the file structure.

* feat: add ROS2 message support and enhance publishing functionality

Implement functions to register, publish, and subscribe to ROS2 messages, improving interoperability with ROS2 systems. Adjust type support to handle both protobuf and ROS2 message types, streamlining the process of message serialization and ensuring robustness in message handling.

* feat: add HTTP and ROS2 example configurations and applications

Introduce example configurations and applications for both HTTP and ROS2 channels, enabling greater flexibility and showcasing the capabilities of the system for publishing and subscribing to messages.

* chore: disable unimplemented copy and move functions

Throw runtime errors for the unimplemented copy and move methods to ensure clarity on their current status and prevent accidental usage.

* feat: enhance message publishing with iteration and logging

Implement continuous publishing of messages with incremental identifiers in the publisher application. Update the subscriber to log message count, improving visibility into received messages.

* refactor: reorganize and encapsulate ROS2 message handling functionality

Streamline the handling of ROS2 messages by encapsulating related functionality into a new utility header file. This enhances code clarity and maintainability while adhering to best practices in modular design.

* refactor: streamline protobuf and ROS2 message publishing

Enhance context handling and unify publish type registration for better clarity and usability. Simplify serialization type management and improve the architecture for future extensions, particularly for messaging features.

* refactor: streamline messaging functions

Enhance message publishing by consolidating function calls and improving clarity. Update context handling to better support message serialization types, allowing for a more flexible interaction with both protobuf and ROS2 messages.

* refactor: simplify message subscription handling and reduce publish frequency

Adjust the message subscription method to handle both Protobuf and ROS2 message types more efficiently and eliminate redundant subscription functions. Decrease the publishing frequency in the example app for improved performance and readability.

* fix: remove unnecessary error message for ROS 1 type detection

Eliminate confusion by removing the printed warning about ROS 1 message types, as it was not providing significant value in handling unsupported types.

* docs: update README and add ros2 channel examples

Clarify publisher behavior in examples and provide comprehensive documentation for ros2 channel implementations.

* feat: support ros2 message types in aimrt_py channel

Add support for ros2 message types in the aimrt_py channel to enhance interoperability with ROS2-based applications.

* fix: improve error handling during ROS2 message serialization and deserialization

Enhance robustness by adding exception handling to ensure the process correctly handles serialization and deserialization failures, improving overall stability in handling ROS2 messages.

* fix: correct argument validation and formatting

Remove unnecessary whitespace and enhance error handling for argument types in the publishing function, ensuring clear type checking and preventing potential runtime errors.

* refactor: streamline message conversion logic

Simplify the conversion of ROS2 messages to Python objects by moving the conversion logic inline. This eliminates unnecessary function calls and improves performance while maintaining clarity in the subscription method.

* feat: add ROS2 support to Python runtime

Enable linking and compilation of ROS2 interfaces based on the build configuration. This improves flexibility by allowing the Python runtime to interface with ROS2 when needed, enhancing interoperability and feature set.

* feat: enhance message type handling for publishers and subscribers

Add utility functions to generate names for ROS2 and protobuf message types, centralizing logic for type name creation. Update the publisher and subscriber to utilize these functions, improving code readability and maintainability while ensuring accurate message type management.

* feat: update message types to use RosTestMsg for better compatibility

Register new message type RosTestMsg in both publisher and subscriber applications. Update data format for publishing events to improve clarity and consistency in message handling. Ensure proper setup in the shell scripts for localization configuration.

* chore: consolidate pybind11 includes

Simplify header files by removing redundant pybind11 includes, reducing clutter and improving maintainability.

* docs: add ROS2 message support details and example links

Enhance the documentation by including information on ROS2 message formats and how to generate corresponding Python code. This provides users with clearer guidance on integrating ROS2 functionality within the project.

* chore: update copyright information to reflect new authorship

Update copyright year and licensing information across multiple configuration files and application scripts to maintain proper attribution for the AimRT project.

* feat: add ROS2 RPC client and server examples with configurations

Introduce example applications for ROS2 RPC communication, including client and server scripts, along with necessary configuration files. Implement a build script to set up the protocol buffers correctly, enhancing usability by supporting both HTTP and gRPC backends.

* feat: update RPC request and response handling

Refactor the RPC client and server implementations to utilize the updated request and response types from the example ROS2 service, improving code clarity and ensuring compatibility with the new service structure.

* refactor: simplify RPC response handling

Remove unnecessary response object instantiation and streamline the response creation process for improved clarity and efficiency in the RPC invocation logic.

* refactor: streamline publisher and subscriber method names

Simplify method names related to publishing and subscribing to enhance readability and maintainability. Transition from a prefix-based naming convention to a more concise approach, ensuring consistency across the codebase.

* chore: update configuration file paths for gRPC client and server examples

Align the configuration file paths in the example scripts to use the gRPC-specific settings, ensuring the correct configuration is utilized for both the client and server.

* refactor: rename internal methods for clarity

Improve naming conventions for protobuf message type functions, enhancing code readability and maintainability. Update related usages to keep consistent across the runtime and RPC generator.

* feat: add service type validation and improve bash scripts

Introduce a new bash script for debugging the ROS2 client and enhance existing scripts with proper sourcing. Implement service type validation to ensure the reliability of service registration. This improves the robustness of RPC interactions and simplifies the setup process for developers.

* feat: extend RPC response data structure and improve logging

Add additional data types to the RosTestRpc response for enhanced functionality. Set logging level to trace for more detailed output during execution.

* feat: add support for static and dynamic arrays in RPC service

Enhance the RPC service by introducing static and dynamic array types for various data types, enabling more complex data handling in requests and responses. This improves flexibility and usability for clients interacting with the service.

* feat: add support for new ROS message types

Enhance the RPC server to handle additional ROS message types, including dynamic and static arrays of custom data structures. This improves the server's data handling capabilities and aligns with the updated service specifications.

* refactor: improve type mapping and message copy functions

Update type mappings for better consistency and clarity. Enhance the readability of message copying functions by formatting multi-line expressions.

* refactor: improve message copying and moving functions

Enhance the handling of ROS message copying and moving by updating the parameter order and adding tailored move functions for better memory management. This optimizes the performance of message manipulations and ensures cleaner, more maintainable code.

* refactor: simplify message type support functions

Remove inline implementations and enhance readability of message type support functions, while maintaining functionality and improving code organization.

* refactor: streamline ROS message handling and improve introspection support

Enhance readability and maintainability by cleaning up function definitions related to ROS message type support. Implement a more robust method for obtaining message members information, addressing potential failure scenarios. Adjust example service implementation for consistency in dynamic data handling.

* fix: ensure pointers for ROS message creation and destruction are valid

Throw runtime exceptions if the function pointers for creating or destroying ROS messages are null, enhancing error handling and stability during object initialization.

* feat: enhance RosTestRpc method signature for better type safety

Improve the RosTestRpc method by adding overloads for different argument configurations, ensuring clearer type expectations and reducing potential runtime errors. This change aids in maintaining robust interactions with the ROS2 service framework.

* refactor: simplify parameter naming in RPC proxy function

Rename the `ctx_ref` parameter to `ctx` in the RPC function signature for cleaner and more concise code. This enhances readability and maintains consistency across the codebase.

* fix: restrict static array sizes in RosTestRpc service

Limit the size of static array fields to three elements each for better resource management and to prevent potential overflow issues.

* feat: enhance python runtime generation for ROS2 services

Add support for generating code from ROS2 service definitions, improving interoperability with the aimrt framework. Update build scripts to include new generation scripts and ensure file dependencies are configured correctly.

* refactor: update RPC service and client implementations to use ROS2 naming convention

Modify scripts to utilize the new aimrt_py-gen-ros2-rpc tool and update service and client references accordingly for consistency and clarity. Remove deprecated code to streamline functionality and improve maintainability.

* chore: update .gitignore to exclude generated files

Add pattern to ignore *_aimrt_rpc_ros2.py files to keep the repository clean from auto-generated files.

* refactor: streamline response structure for RosTestRpc

Remove unused fields from RosTestRpc response to simplify the data structure and enhance clarity. Adjust logging level for better control over output verbosity.

* chore: update log level to INFO for consistency

Adjust the core log level to INFO to ensure a more standard logging output and reduce unnecessary verbosity in the logs.

* docs: add README for ros2 rpc examples

Provide detailed instructions and explanations for setting up and running Python RPC examples using ros2 protocols with HTTP, gRPC, and native ros2 backends.

* feat: enhance aimrt_py channel with rpc support for ros2 message types

Add support for ros2 message types in both aimrt_py channel and rpc to improve compatibility and functionality.

* docs: add ros2_rpc example to Python interface section

Include a new example link to enhance the documentation and provide more resources for users exploring the Python interfaces.

* chore: add spacing for improved readability in service proxy class

Improve code readability by adding blank lines in the service proxy class definition.

* chore: standardize argument names for RPC generator

Update argument names in the RPC generation script and implementation for consistency, enhancing readability and usability.

* docs: update RPC documentation to include ROS2 Srv support

Add examples and instructions for using ROS2 Srv in RPC, enhancing integration options for developers. Adjust existing protobuf references for clarity and consistency.

* style: standardize whitespace in RosTestRpc.srv

Improve readability by ensuring consistent spacing in the service definition.

* build: exclude ROS2 specific files from the build if not using ROS2

Remove ROS2-related source files from the compilation when the project is configured without ROS2 support, helping to streamline the build process and avoid unnecessary dependencies.

* style: add missing newline at end of file for consistency

* refactor: rename copy function to improve clarity

Refactor the function used for copying messages within dynamic arrays to better convey its functionality. This change enhances code readability and maintainability.

---------

Co-authored-by: zhangyi <zhangyi@agibot.com>
This commit is contained in:
zhangyi1357 2024-11-21 11:48:44 +08:00 committed by GitHub
parent 1b88aa7bf2
commit df5d21e49c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
59 changed files with 2526 additions and 272 deletions

1
.gitignore vendored
View File

@ -23,6 +23,7 @@ dist/
__pycache__/
*_pb2.py
*_pb2_grpc.py
*_aimrt_rpc_ros2.py
log/
tmp/

View File

@ -10,6 +10,7 @@
- 新增 aimrt_cli trans 命令,用于将 使用 aimrt record_playback 插件录制的 bag 文件转换为 ros2 的 bag 文件;
- 新增 Echo 插件,用于回显消息;
- 新增了基于执行器的定时器,方便执行定时任务;
- aimrt_py channel 和 rpc 支持 ros2 消息类型;
**次要修改**
- 缩短了一些 examples 的文件路径长度;

View File

@ -9,6 +9,8 @@ AimRT 提供了以下 Python 接口使用示例:
- {{ '[pb_rpc]({}/src/examples/py/pb_rpc)'.format(code_site_root_path_url) }}
- {{ '[pb_chn_bench]({}/src/examples/py/pb_chn_bench)'.format(code_site_root_path_url) }}
- {{ '[pb_rpc_bench]({}/src/examples/py/pb_rpc_bench)'.format(code_site_root_path_url) }}
- {{ '[ros2_chn]({}/src/examples/py/ros2_chn)'.format(code_site_root_path_url) }}
- {{ '[ros2_rpc]({}/src/examples/py/ros2_rpc)'.format(code_site_root_path_url) }}
关于这些示例的说明:
- 每个示例都有自己独立的 readme 文档,详情请点击示例链接进入后查看;

View File

@ -7,11 +7,12 @@
- {{ '[examples_py_pb_chn_publisher_app.py]({}/src/examples/py/pb_chn/examples_py_pb_chn_publisher_app.py)'.format(code_site_root_path_url) }}
- {{ '[examples_py_pb_chn_subscriber_app.py]({}/src/examples/py/pb_chn/examples_py_pb_chn_subscriber_app.py)'.format(code_site_root_path_url) }}
## protobuf 协议
## 协议
协议用于确定通信各端的消息格式。一般来说,协议都是使用一种与具体的编程语言无关的 IDL ( Interface description language )描述,然后由某种工具转换为各个语言的代码。
### Protobuf
[Protobuf](https://protobuf.dev/)是一种由 Google 开发的、用于序列化结构化数据的轻量级、高效的数据交换格式,是一种广泛使用的 IDL。
当前版本 AimRT Python 只支持 protobuf 协议。在使用 AimRT Python 发送/订阅消息之前,使用者需要先基于 protobuf 协议生成一些 python 的桩代码。
@ -36,6 +37,32 @@ protoc --python_out=. example.proto
这将生成`example_pb2.py`文件,包含了根据定义的消息类型生成的 Python 接口,我们的业务代码中需要 import 此文件。
### ROS2 Message
ROS2 Message 是一种用于在 ROS2 中进行通信和数据交换的结构化数据格式。在使用时,开发者需要先定义一个 ROS2 Package在其中定义一个`.msg`文件,比如`example.msg`
```
int32 num
float32 num2
char data
```
然后直接通过 ROS2 提供的 CMake 方法`rosidl_generate_interfaces`,为消息生成 C++ 代码和 CMake Target例如
```cmake
rosidl_generate_interfaces(
example_ros2_msg_gencode
"msg/example.msg"
)
```
构建之后,还需要设置相应的环境变量,才能在 python 中使用生成的消息类型,在 aimrt 的 build 目录中,执行:
```bash
source install/share/example_ros2/setup.bash
```
关于更多生成自定义 ROS2 Message 的详细信息,请参考[ROS2 官方文档](https://docs.ros.org/en/humble/Tutorials/Beginner-Client-Libraries/Custom-ROS2-Interfaces.html)。
## ChannelHandle
模块可以通过调用`CoreRef`句柄的`GetChannelHandle()`接口,获取`ChannelHandleRef`句柄,来使用 Channel 功能。其提供的核心接口如下:

View File

@ -6,8 +6,10 @@
参考示例:
- {{ '[examples_py_pb_rpc_client_app.py]({}/src/examples/py/pb_rpc/examples_py_pb_rpc_client_app.py)'.format(code_site_root_path_url) }}
- {{ '[examples_py_pb_rpc_server_app.py]({}/src/examples/py/pb_rpc/examples_py_pb_rpc_server_app.py)'.format(code_site_root_path_url) }}
- {{ '[examples_py_ros2_rpc_client_app.py]({}/src/examples/py/ros2_rpc/examples_py_ros2_rpc_client_app.py)'.format(code_site_root_path_url) }}
- {{ '[examples_py_ros2_rpc_server_app.py]({}/src/examples/py/ros2_rpc/examples_py_ros2_rpc_server_app.py)'.format(code_site_root_path_url) }}
## protobuf 协议
## 协议
协议用于确定 RPC 中客户端和服务端的消息格式。一般来说,协议都是使用一种与具体的编程语言无关的 IDL ( Interface description language )描述,然后由某种工具转换为各个语言的代码。对于 RPC 来说,这里需要两个步骤:
@ -15,11 +17,10 @@
- 开发者需要使用 AimRT 提供的工具,为协议文件中**服务定义**生成指定编程语言中的代码;
### Protobuf
[Protobuf](https://protobuf.dev/)是一种由 Google 开发的、用于序列化结构化数据的轻量级、高效的数据交换格式,是一种广泛使用的 IDL。它不仅能够描述消息结构还提供了`service`语句来定义 RPC 服务。
当前版本 AimRT Python 只支持 protobuf 协议。在使用 AimRT Python 发起/处理 RPC 请求之前,使用者需要先基于 protobuf 协议生成一些 python 的桩代码。
在使用时,开发者需要先定义一个`.proto`文件,在其中定义消息结构和 RPC 服务。例如`rpc.proto`
```protobuf
@ -57,6 +58,29 @@ protoc --aimrt_rpc_out=. --plugin=protoc-gen-aimrt_rpc=./protoc_plugin_py_gen_ai
这将生成`rpc_aimrt_rpc_pb2.py`文件,包含了根据定义的服务生成的 Python 接口,我们的业务代码中需要 import 此文件。
### ROS2 Srv
ROS2 Srv 是一种用于在 ROS2 中进行 RPC 定义的格式。在使用时,开发者需要先定义一个 ROS2 Package在其中定义一个`.srv`文件,比如`example.srv`
```
byte[] data
---
int64 code
```
其中,以`---`来分割 Req 和 Rsp 的定义。
aimrt_py 安装时会自动安装一个命令行工具 `aimrt_py-gen-ros2-rpc`,用于根据 ROS2 Srv 文件生成 AimRT Python 的 RPC 桩代码。
```shell
aimrt_py-gen-ros2-rpc --pkg_name=example_pkg --srv_file=./example.srv --output_path=./
```
其中 pkg_name 是 ROS2 Package 的名称srv_file 是 ROS2 Srv 文件的路径output_path 是生成的桩代码的输出路径。这将生成一个`example_aimrt_rpc_ros2.py`文件,包含了根据定义的服务生成的 Python 接口,我们的业务代码中需要 import 此文件。
需要注意的是,`aimrt_py-gen-ros2-rpc` 只会生成 Req 和 Rsp 的定义,不会生成消息结构部分的代码,消息结构部分代码仍然需要使用者自行生成(即构建 ROS2 Package 并生成消息结构代码, 详情可以参考 [ROS2 官方文档](https://docs.ros.org/en/humble/Tutorials/Beginner-Client-Libraries/Custom-ROS2-Interfaces.html))。
## RpcHandle
模块可以通过调用`CoreRef`句柄的`GetRpcHandle()`接口,获取`RpcHandleRef`句柄。一般情况下,开发者不会直接使用`RpcHandleRef`直接提供的接口,而是根据 RPC IDL 文件生成一些桩代码,对`RpcHandleRef`句柄做一些封装,然后在业务代码中使用这些经过封装的接口。
@ -126,7 +150,7 @@ RpcContext 是 RPC 调用时上下文信息,开发者可以在 RPC 调用时
以下是一个使用 AimRT Python 进行 RPC Client 调用的示例,通过 Create Module 方式拿到`CoreRef`句柄。如果是基于`Module`模式在`Initialize`方法中拿到`CoreRef`句柄,使用方式也类似:
以下是一个使用 AimRT Python 基于 protobuf 协议进行 RPC Client 调用的示例,通过 Create Module 方式拿到`CoreRef`句柄。如果是基于`Module`模式在`Initialize`方法中拿到`CoreRef`句柄,使用方式也类似:
```python
import aimrt_py
@ -200,7 +224,7 @@ if __name__ == '__main__':
- **Step 2**:在`Initialize`阶段调用`RpcHandleRef``RegisterService`方法注册 RPC Service
以下是一个使用 AimRT Python 进行 RPC Service 处理的示例,通过 Create Module 方式拿到`CoreRef`句柄。如果是基于`Module`模式在`Initialize`方法中拿到`CoreRef`句柄,使用方式也类似:
以下是一个使用 AimRT Python 基于 protobuf 协议进行 RPC Service 处理的示例,通过 Create Module 方式拿到`CoreRef`句柄。如果是基于`Module`模式在`Initialize`方法中拿到`CoreRef`句柄,使用方式也类似:
```python
import aimrt_py
@ -281,3 +305,9 @@ def main():
if __name__ == '__main__':
main()
```
基于 ROS2 Srv 协议的 RPC 调用和处理,除数据类型不同外,其他使用方式与基于 protobuf 协议的 RPC 调用和处理基本一致。
完整示例请参考:
- {{ '[examples/py/ros2_rpc]({}/src/examples/py/ros2_rpc)'.format(code_site_root_path_url) }}
- {{ '[examples/py/pb_rpc]({}/src/examples/py/pb_rpc)'.format(code_site_root_path_url) }}

View File

@ -33,7 +33,7 @@
说明:
- 此示例创建了以下两个模块:
- `NormalPublisherPyModule`:会在启动后向配置的 topic 中发布`ExampleEventMsg` 类型的消息,然后结束进程;
- `NormalPublisherPyModule`:会在启动后向配置的 topic 中发布若干`ExampleEventMsg` 类型的消息,然后结束进程;
- `NormalSubscriberPyModule`:会订阅配置的 topic 下的 `ExampleEventMsg` 类型的消息;
- 此示例使用 http 类型的 channel 后端进行通信,请确保相关端口未被占用;
@ -70,7 +70,7 @@
说明:
- 此示例创建了以下两个模块:
- `NormalPublisherPyModule`:会在启动后向配置的 topic 中发布`ExampleEventMsg` 类型的消息,然后结束进程;
- `NormalPublisherPyModule`:会在启动后向配置的 topic 中发布若干`ExampleEventMsg` 类型的消息,然后结束进程;
- `NormalSubscriberPyModule`:会订阅配置的 topic 下的 `ExampleEventMsg` 类型的消息;
- 此示例使用 ros2 类型的 channel 后端进行通信,请确保本地安装有 ROS2 Humble

View File

@ -47,8 +47,9 @@ def main():
# Sleep for seconds
time.sleep(1)
# Publish event
event_msg = event_pb2.ExampleEventMsg()
# Publish event
event_msg.msg = "Publish without ctx or serialization_type"
event_msg.num = 1
aimrt_py.Publish(publisher, event_msg)

View File

@ -0,0 +1,71 @@
# ros2 channel examples
## ros2 channel http
一个基于 ros2 协议与 http 后端的 python channel 示例,演示内容包括:
- 如何在 python 中使用 ros2 协议作为 channel 传输协议;
- 如何基于 aimrt_py 创建模块的方式使用 Channel publish 和 subscribe 功能;
- 如何使用 http 类型的 channel 后端;
核心代码:
- [examples_py_ros2_chn_publisher_app.py](./examples_py_ros2_chn_publisher_app.py)
- [examples_py_ros2_chn_subscriber_app.py](./examples_py_ros2_chn_subscriber_app.py)
配置文件:
- [examples_py_ros2_chn_http_pub_cfg.yaml](./cfg/examples_py_ros2_chn_http_pub_cfg.yaml)
- [examples_py_ros2_chn_http_sub_cfg.yaml](./cfg/examples_py_ros2_chn_http_sub_cfg.yaml)
运行方式linux
- [安装 `aimrt_py` 包](../../../../document/sphinx-cn/tutorials/quick_start/installation_py.md)
- 运行本目录下的[start_examples_py_ros2_chn_http_sub.sh](./start_examples_py_ros2_chn_http_sub.sh)脚本,启动 subscriber
- 在新终端里运行本目录下的[start_examples_py_ros2_chn_http_pub.sh](./start_examples_py_ros2_chn_http_pub.sh)脚本,启动 publisher 发布一条消息;
- 向 subscriber 进程所在终端里键入`ctrl-c`以停止进程;
说明:
- 此示例创建了以下两个模块:
- `NormalPublisherPyModule`:会在启动后向配置的 topic 中发布若干条 `ExampleEventMsg` 类型的消息,然后结束进程;
- `NormalSubscriberPyModule`:会订阅配置的 topic 下的 `ExampleEventMsg` 类型的消息;
- 此示例使用 http 类型的 channel 后端进行通信,请确保相关端口未被占用;
## ros2 channel ros2
一个基于 ros2 协议与 ros2 后端的 python channel 示例,演示内容包括:
- 如何在 python 中使用 ros2 协议作为 channel 传输协议;
- 如何基于 aimrt_py 创建模块的方式使用 Channel publish 和 subscribe 功能;
- 如何使用 ros2 类型的 channel 后端;
核心代码:
- [examples_py_ros2_chn_publisher_app.py](./examples_py_ros2_chn_publisher_app.py)
- [examples_py_ros2_chn_subscriber_app.py](./examples_py_ros2_chn_subscriber_app.py)
配置文件:
- [examples_py_ros2_chn_ros2_pub_cfg.yaml](./cfg/examples_py_ros2_chn_ros2_pub_cfg.yaml)
- [examples_py_ros2_chn_ros2_sub_cfg.yaml](./cfg/examples_py_ros2_chn_ros2_sub_cfg.yaml)
运行方式linux
- [安装 `aimrt_py` 包](../../../../document/sphinx-cn/tutorials/quick_start/installation_py.md)
- 运行本目录下的[start_examples_py_ros2_chn_ros2_sub.sh](./start_examples_py_ros2_chn_ros2_sub.sh)脚本,启动 subscriber
- 在新终端里运行本目录下的[start_examples_py_pb_chn_ros2_pub.sh](./start_examples_py_pb_chn_ros2_pub.sh)脚本,启动 publisher 发布一条消息;
- 向 subscriber 进程所在终端里键入`ctrl-c`以停止进程;
说明:
- 此示例创建了以下两个模块:
- `NormalPublisherPyModule`:会在启动后向配置的 topic 中发布若干条 `ExampleEventMsg` 类型的消息,然后结束进程;
- `NormalSubscriberPyModule`:会订阅配置的 topic 下的 `ExampleEventMsg` 类型的消息;
- 此示例使用 ros2 类型的 channel 后端进行通信,请确保本地安装有 ROS2 Humble

View File

@ -0,0 +1,35 @@
# Copyright (c) 2024 The AimRT Authors.
# AimRT is licensed under Mulan PSL v2.
aimrt:
plugin:
plugins:
- name: net_plugin
path: ${AIMRT_PLUGIN_DIR}/libaimrt_net_plugin.so
options:
thread_num: 4
http_options:
listen_ip: 127.0.0.1
listen_port: 50081
log:
core_lvl: INFO # Trace/Debug/Info/Warn/Error/Fatal/Off
default_module_lvl: INFO
backends:
- type: console
channel:
backends:
- type: http
options:
pub_topics_options:
- topic_name: "(.*)"
server_url_list: ["http://127.0.0.1:50080"]
pub_topics_options:
- topic_name: "(.*)"
enable_backends: [http]
module:
modules:
- name: NormalPublisherPyModule
log_lvl: INFO
NormalPublisherPyModule:
topic_name: test_topic

View File

@ -0,0 +1,31 @@
# Copyright (c) 2024 The AimRT Authors.
# AimRT is licensed under Mulan PSL v2.
aimrt:
plugin:
plugins:
- name: net_plugin
path: ${AIMRT_PLUGIN_DIR}/libaimrt_net_plugin.so
options:
thread_num: 4
http_options:
listen_ip: 127.0.0.1
listen_port: 50080
log:
core_lvl: INFO # Trace/Debug/Info/Warn/Error/Fatal/Off
default_module_lvl: INFO
backends:
- type: console
channel:
backends:
- type: http
sub_topics_options:
- topic_name: "(.*)"
enable_backends: [http]
module:
modules:
- name: NormalSubscriberPyModule
log_lvl: INFO
NormalSubscriberPyModule:
topic_name: test_topic

View File

@ -0,0 +1,30 @@
# Copyright (c) 2024 The AimRT Authors.
# AimRT is licensed under Mulan PSL v2.
aimrt:
plugin:
plugins:
- name: ros2_plugin
path: ${AIMRT_PLUGIN_DIR}/libaimrt_ros2_plugin.so
options:
node_name: example_ros2_chn_py_publisher_node
executor_type: MultiThreaded # SingleThreaded/StaticSingleThreaded/MultiThreaded
executor_thread_num: 2
log:
core_lvl: INFO # Trace/Debug/Info/Warn/Error/Fatal/Off
default_module_lvl: INFO
backends:
- type: console
channel:
backends:
- type: ros2
pub_topics_options:
- topic_name: "(.*)"
enable_backends: [ros2]
module:
modules:
- name: NormalPublisherPyModule
log_lvl: INFO
NormalPublisherPyModule:
topic_name: test_topic

View File

@ -0,0 +1,30 @@
# Copyright (c) 2024 The AimRT Authors.
# AimRT is licensed under Mulan PSL v2.
aimrt:
plugin:
plugins:
- name: ros2_plugin
path: ${AIMRT_PLUGIN_DIR}/libaimrt_ros2_plugin.so
options:
node_name: example_ros2_chn_py_subscriber_node
executor_type: MultiThreaded # SingleThreaded/StaticSingleThreaded/MultiThreaded
executor_thread_num: 2
log:
core_lvl: INFO # Trace/Debug/Info/Warn/Error/Fatal/Off
default_module_lvl: INFO
backends:
- type: console
channel:
backends:
- type: ros2
sub_topics_options:
- topic_name: "(.*)"
enable_backends: [ros2]
module:
modules:
- name: NormalSubscriberPyModule
log_lvl: INFO
NormalSubscriberPyModule:
topic_name: test_topic

View File

@ -0,0 +1,94 @@
# Copyright (c) 2024 The AimRT Authors.
# AimRT is licensed under Mulan PSL v2.
import argparse
import threading
import time
import aimrt_py
import yaml
from example_ros2.msg import RosTestMsg
def main():
parser = argparse.ArgumentParser(description='Example channel publisher app.')
parser.add_argument('--cfg_file_path', type=str, default="", help='config file path')
args = parser.parse_args()
print("AimRT start.")
aimrt_core = aimrt_py.Core()
# Initialize
core_options = aimrt_py.CoreOptions()
core_options.cfg_file_path = args.cfg_file_path
aimrt_core.Initialize(core_options)
# Create Module
module_handle = aimrt_core.CreateModule("NormalPublisherPyModule")
# Read cfg
module_cfg_file_path = module_handle.GetConfigurator().GetConfigFilePath()
with open(module_cfg_file_path, 'r') as file:
data = yaml.safe_load(file)
topic_name = str(data["topic_name"])
# Register publish type
publisher = module_handle.GetChannelHandle().GetPublisher(topic_name)
assert publisher, f"Get publisher for topic '{topic_name}' failed."
aimrt_py.RegisterPublishType(publisher, RosTestMsg)
# Start
thread = threading.Thread(target=aimrt_core.Start)
thread.start()
# Sleep for seconds
time.sleep(1)
msg = RosTestMsg()
msg.num = 1000
# Publish event
msg.data = [bytes([x]) for x in [1, 2, 3, 4]]
aimrt_py.Publish(publisher, msg)
aimrt_py.info(module_handle.GetLogger(),
f"Publish new ros2 message, data: {msg.data}")
# Publish event with ros2 serialization
msg.data = [bytes([x]) for x in [5, 6, 7, 8]]
aimrt_py.Publish(publisher, "ros2", msg)
aimrt_py.info(module_handle.GetLogger(),
f"Publish new ros2 message, data: {msg.data}")
# Publish event with context
ctx = aimrt_py.Context()
ctx.SetMetaValue("key1", "value1")
msg.data = [bytes([x]) for x in [9, 10, 11, 12]]
aimrt_py.Publish(publisher, ctx, msg)
aimrt_py.info(module_handle.GetLogger(),
f"Publish new ros2 message, data: {msg.data}")
# Publish event with context ref
ctx.Reset() # Reset context, then it can be used again
ctx_ref = aimrt_py.ContextRef(ctx)
ctx_ref.SetMetaValue("key2", "value2")
ctx_ref.SetSerializationType("ros2")
msg.data = [bytes([x]) for x in [13, 14, 15, 16]]
aimrt_py.Publish(publisher, ctx_ref, msg)
aimrt_py.info(module_handle.GetLogger(),
f"Publish new ros2 message, data: {msg.data}")
# Sleep for seconds
time.sleep(1)
# Shutdown
aimrt_core.Shutdown()
thread.join()
print("AimRT exit.")
if __name__ == '__main__':
main()

View File

@ -0,0 +1,80 @@
# Copyright (c) 2024 The AimRT Authors.
# AimRT is licensed under Mulan PSL v2.
import argparse
import signal
import sys
import threading
import aimrt_py
import yaml
from example_ros2.msg import RosTestMsg
global_aimrt_core = None
def signal_handler(sig, frame):
global global_aimrt_core
if (global_aimrt_core and (sig == signal.SIGINT or sig == signal.SIGTERM)):
global_aimrt_core.Shutdown()
return
sys.exit(0)
def main():
parser = argparse.ArgumentParser(description='Example channel subscriber app.')
parser.add_argument('--cfg_file_path', type=str, default="", help='config file path')
args = parser.parse_args()
signal.signal(signal.SIGINT, signal_handler)
signal.signal(signal.SIGTERM, signal_handler)
print("AimRT start.")
aimrt_core = aimrt_py.Core()
global global_aimrt_core
global_aimrt_core = aimrt_core
# Initialize
core_options = aimrt_py.CoreOptions()
core_options.cfg_file_path = args.cfg_file_path
aimrt_core.Initialize(core_options)
# Create Module
module_handle = aimrt_core.CreateModule("NormalSubscriberPyModule")
# Read cfg
module_cfg_file_path = module_handle.GetConfigurator().GetConfigFilePath()
with open(module_cfg_file_path, 'r') as file:
data = yaml.safe_load(file)
topic_name = str(data["topic_name"])
# Subscribe
subscriber = module_handle.GetChannelHandle().GetSubscriber(topic_name)
assert subscriber, f"Get subscriber for topic '{topic_name}' failed."
count = 0
def EventHandle(ctx_ref: aimrt_py.ContextRef, msg: RosTestMsg):
nonlocal count
count += 1
aimrt_py.info(module_handle.GetLogger(),
f"Get new ros2 message, ctx: {ctx_ref.ToString()}, data: {msg.data}, count: {count}")
aimrt_py.Subscribe(subscriber, RosTestMsg, EventHandle)
# Start
thread = threading.Thread(target=aimrt_core.Start)
thread.start()
while thread.is_alive():
thread.join(1.0)
print("AimRT exit.")
if __name__ == '__main__':
main()

View File

@ -0,0 +1,6 @@
#!/bin/bash
export AIMRT_PLUGIN_DIR=$(pip show aimrt_py | grep Location | awk '{print $2}')/aimrt_py
source $AIMRT_PLUGIN_DIR/share/example_ros2/local_setup.bash
python3 ./examples_py_ros2_chn_publisher_app.py --cfg_file_path=./cfg/examples_py_ros2_chn_http_pub_cfg.yaml

View File

@ -0,0 +1,6 @@
#!/bin/bash
export AIMRT_PLUGIN_DIR=$(pip show aimrt_py | grep Location | awk '{print $2}')/aimrt_py
source $AIMRT_PLUGIN_DIR/share/example_ros2/local_setup.bash
python3 ./examples_py_ros2_chn_subscriber_app.py --cfg_file_path=./cfg/examples_py_ros2_chn_http_sub_cfg.yaml

View File

@ -0,0 +1,7 @@
#!/bin/bash
export AIMRT_PLUGIN_DIR=$(pip show aimrt_py | grep Location | awk '{print $2}')/aimrt_py
source ${AIMRT_PLUGIN_DIR}/share/ros2_plugin_proto/local_setup.bash
python3 ./examples_py_ros2_chn_publisher_app.py --cfg_file_path=./cfg/examples_py_ros2_chn_ros2_pub_cfg.yaml

View File

@ -0,0 +1,7 @@
#!/bin/bash
export AIMRT_PLUGIN_DIR=$(pip show aimrt_py | grep Location | awk '{print $2}')/aimrt_py
source ${AIMRT_PLUGIN_DIR}/share/ros2_plugin_proto/local_setup.bash
python3 ./examples_py_ros2_chn_subscriber_app.py --cfg_file_path=./cfg/examples_py_ros2_chn_ros2_sub_cfg.yaml

View File

@ -0,0 +1,115 @@
# ros2 rpc examples
## ros2 rpc http
一个基于 ros2 协议与 http 后端的 python rpc 示例,演示内容包括:
- 如何在 python 中使用 ros2 协议作为 rpc 传输协议;
- 如何基于 aimrt_py 创建模块的方式使用 rpc client 和 server 功能;
- 如何使用 http 类型的 rpc 后端;
核心代码:
- [RosTestRpc.srv](../../../protocols/example_ros2/srv/RosTestRpc.srv)
- [examples_py_ros2_rpc_client_app.py](./examples_py_ros2_rpc_client_app.py)
- [examples_py_ros2_rpc_server_app.py](./examples_py_ros2_rpc_server_app.py)
配置文件:
- [examples_py_ros2_rpc_http_client_cfg.yaml](./cfg/examples_py_ros2_rpc_http_client_cfg.yaml)
- [examples_py_ros2_rpc_http_server_cfg.yaml](./cfg/examples_py_ros2_rpc_http_server_cfg.yaml)
运行方式linux
- [安装 `aimrt_py` 包](../../../../document/sphinx-cn/tutorials/quick_start/installation_py.md)
- 运行本目录下的[build_examples_py_ros2_rpc.sh](./build_examples_py_ros2_rpc.sh)脚本,生成协议桩代码文件;
- 注意生成的 rpc 桩代码文件会引用 `example_ros2` 包,请确保本地安装有 ROS2 Humble然后打开选项编译本项目生成对应的 `example_ros2` 包,否则无法正常运行本示例;
- 运行本目录下的[start_examples_py_ros2_rpc_http_server.sh](./start_examples_py_ros2_rpc_http_server.sh)脚本,启动 RPC Server
- 在新终端里运行本目录下的[start_examples_py_ros2_rpc_http_client.sh](./start_examples_py_ros2_rpc_http_client.sh)脚本,启动 Rpc client 发起一次请求;
- 向 RPC Server 进程所在终端里键入`ctrl-c`以停止进程;
说明:
- 此示例创建了以下两个模块:
- `NormalRpcClientPyModule`:会在启动后向 `RosTestRpc` 发起一次 `RosTestRpc` 的 RPC 请求,等收到回包并解析后结束进程;
- `NormalRpcServerPymodule`:会注册 `RosTestRpc` 服务端,通过 Server 接口,打印接收到的数据;
- 此示例使用 http 类型的 rpc 后端进行通信,请确保相关端口未被占用;
## ros2 rpc grpc
一个基于 ros2 协议与 grpc 后端的 python rpc 示例,演示内容包括:
- 如何在 python 中使用 ros2 协议作为 rpc 传输协议;
- 如何基于 aimrt_py 创建模块的方式使用 rpc client 和 server 功能;
- 如何使用 grpc 类型的 rpc 后端;
核心代码:
- [RosTestRpc.srv](../../../protocols/example_ros2/srv/RosTestRpc.srv)
- [examples_py_ros2_rpc_client_app.py](./examples_py_ros2_rpc_client_app.py)
- [examples_py_ros2_rpc_server_app.py](./examples_py_ros2_rpc_server_app.py)
配置文件:
- [examples_py_ros2_rpc_grpc_client_cfg.yaml](./cfg/examples_py_ros2_rpc_grpc_client_cfg.yaml)
- [examples_py_ros2_rpc_grpc_server_cfg.yaml](./cfg/examples_py_ros2_rpc_grpc_server_cfg.yaml)
运行方式linux
- [安装 `aimrt_py` 包](../../../../document/sphinx-cn/tutorials/quick_start/installation_py.md)
- 运行本目录下的[build_examples_py_ros2_rpc.sh](./build_examples_py_ros2_rpc.sh)脚本,生成协议桩代码文件;
- 注意生成的 rpc 桩代码文件会引用 `example_ros2` 包,请确保本地安装有 ROS2 Humble然后打开选项编译本项目生成对应的 `example_ros2` 包,否则无法正常运行本示例;
- 运行本目录下的[start_examples_py_ros2_rpc_grpc_server.sh](./start_examples_py_ros2_rpc_grpc_server.sh)脚本,启动 RPC Server
- 在新终端里运行本目录下的[start_examples_py_ros2_rpc_grpc_client.sh](./start_examples_py_ros2_rpc_grpc_client.sh)脚本,启动 Rpc client 发起一次请求;
- 向 RPC Server 进程所在终端里键入`ctrl-c`以停止进程;
说明:
- 此示例创建了以下两个模块:
- `NormalRpcClientPyModule`:会在启动后向 `RosTestRpc` 发起一次 `RosTestRpc` 的 RPC 请求,等收到回包并解析后结束进程;
- `NormalRpcServerPymodule`:会注册 `RosTestRpc` 服务端,通过 Server 接口,打印接收到的数据;
- 此示例使用 grpc 类型的 rpc 后端进行通信,请确保相关端口未被占用;
## ros2 rpc ros2
一个基于 ros2 协议与 ros2 后端的 python rpc 示例,演示内容包括:
- 如何在 python 中使用 ros2 协议作为 rpc 传输协议;
- 如何基于 aimrt_py 创建模块的方式使用 rpc client 和 server 功能;
- 如何使用 ros2 类型的 rpc 后端;
核心代码:
- [RosTestRpc.srv](../../../protocols/example_ros2/srv/RosTestRpc.srv)
- [examples_py_ros2_rpc_client_app.py](./examples_py_ros2_rpc_client_app.py)
- [examples_py_ros2_rpc_server_app.py](./examples_py_ros2_rpc_server_app.py)
配置文件:
- [examples_py_ros2_rpc_ros2_client_cfg.yaml](./cfg/examples_py_ros2_rpc_ros2_client_cfg.yaml)
- [examples_py_ros2_rpc_ros2_server_cfg.yaml](./cfg/examples_py_ros2_rpc_ros2_server_cfg.yaml)
运行方式linux
- [安装 `aimrt_py` 包](../../../../document/sphinx-cn/tutorials/quick_start/installation_py.md)
- 运行本目录下的[build_examples_py_ros2_rpc.sh](./build_examples_py_ros2_rpc.sh)脚本,生成协议桩代码文件;
- 注意生成的 rpc 桩代码文件会引用 `example_ros2` 包,请确保本地安装有 ROS2 Humble然后打开选项编译本项目生成对应的 `example_ros2` 包,否则无法正常运行本示例;
- 运行本目录下的[start_examples_py_ros2_rpc_ros2_server.sh](./start_examples_py_ros2_rpc_ros2_server.sh)脚本,启动 RPC Server
- 在新终端里运行本目录下的[start_examples_py_ros2_rpc_ros2_client.sh](./start_examples_py_ros2_rpc_ros2_client.sh)脚本,启动 Rpc client 发起一次请求;
- 向 RPC Server 进程所在终端里键入`ctrl-c`以停止进程;
说明:
- 此示例创建了以下两个模块:
- `NormalRpcClientPyModule`:会在启动后向 `RosTestRpc` 发起一次 `RosTestRpc` 的 RPC 请求,等收到回包并解析后结束进程;
- `NormalRpcServerPymodule`:会注册 `RosTestRpc` 服务端,通过 Server 接口,打印接收到的数据;
- 此示例使用 ros2 类型的 rpc 后端进行通信,请确保本地安装有 ROS2 Humble

View File

@ -0,0 +1,5 @@
#!/bin/bash
protocols_dir=../../../protocols/example_ros2/
aimrt_py-gen-ros2-rpc --pkg_name=example_ros2 --srv_file=${protocols_dir}/RosTestRpc.srv --output_path=./

View File

@ -0,0 +1,31 @@
# Copyright (c) 2024 The AimRT Authors.
# AimRT is licensed under Mulan PSL v2.
aimrt:
plugin:
plugins:
- name: grpc_plugin
path: ${AIMRT_PLUGIN_DIR}/libaimrt_grpc_plugin.so
options:
thread_num: 4
listen_ip: 127.0.0.1
listen_port: 50051
log:
core_lvl: INFO # Trace/Debug/Info/Warn/Error/Fatal/Off
default_module_lvl: INFO
backends:
- type: console
rpc:
backends:
- type: grpc
options:
clients_options:
- func_name: "(.*)"
server_url: http://127.0.0.1:50050
clients_options:
- func_name: "(.*)"
enable_backends: [grpc]
module:
modules:
- name: NormalRpcClientPyModule
log_lvl: INFO

View File

@ -0,0 +1,27 @@
# Copyright (c) 2024 The AimRT Authors.
# AimRT is licensed under Mulan PSL v2.
aimrt:
plugin:
plugins:
- name: grpc_plugin
path: ${AIMRT_PLUGIN_DIR}/libaimrt_grpc_plugin.so
options:
thread_num: 4
listen_ip: 127.0.0.1
listen_port: 50050
log:
core_lvl: INFO # Trace/Debug/Info/Warn/Error/Fatal/Off
default_module_lvl: INFO
backends:
- type: console
rpc:
backends:
- type: grpc
servers_options:
- func_name: "(.*)"
enable_backends: [grpc]
module:
modules:
- name: NormalRpcServerPyModule
log_lvl: INFO

View File

@ -0,0 +1,32 @@
# Copyright (c) 2024 The AimRT Authors.
# AimRT is licensed under Mulan PSL v2.
aimrt:
plugin:
plugins:
- name: net_plugin
path: ${AIMRT_PLUGIN_DIR}/libaimrt_net_plugin.so
options:
thread_num: 4
http_options:
listen_ip: 127.0.0.1
listen_port: 50081
log:
core_lvl: Trace # Trace/Debug/Info/Warn/Error/Fatal/Off
default_module_lvl: INFO
backends:
- type: console
rpc:
backends:
- type: http
options:
clients_options:
- func_name: "(.*)"
server_url: http://127.0.0.1:50080
clients_options:
- func_name: "(.*)"
enable_backends: [http]
module:
modules:
- name: NormalRpcClientPyModule
log_lvl: INFO

View File

@ -0,0 +1,28 @@
# Copyright (c) 2024 The AimRT Authors.
# AimRT is licensed under Mulan PSL v2.
aimrt:
plugin:
plugins:
- name: net_plugin
path: ${AIMRT_PLUGIN_DIR}/libaimrt_net_plugin.so
options:
thread_num: 4
http_options:
listen_ip: 127.0.0.1
listen_port: 50080
log:
core_lvl: INFO # Trace/Debug/Info/Warn/Error/Fatal/Off
default_module_lvl: INFO
backends:
- type: console
rpc:
backends:
- type: http
servers_options:
- func_name: "(.*)"
enable_backends: [http]
module:
modules:
- name: NormalRpcServerPyModule
log_lvl: INFO

View File

@ -0,0 +1,27 @@
# Copyright (c) 2024 The AimRT Authors.
# AimRT is licensed under Mulan PSL v2.
aimrt:
plugin:
plugins:
- name: ros2_plugin
path: ${AIMRT_PLUGIN_DIR}/libaimrt_ros2_plugin.so
options:
node_name: example_ros2_rpc_client_node
executor_type: MultiThreaded # SingleThreaded/StaticSingleThreaded/MultiThreaded
executor_thread_num: 2
log:
core_lvl: INFO # Trace/Debug/Info/Warn/Error/Fatal/Off
default_module_lvl: INFO
backends:
- type: console
rpc:
backends:
- type: ros2
clients_options:
- func_name: "(.*)"
enable_backends: [ros2]
module:
modules:
- name: NormalRpcClientPyModule
log_lvl: INFO

View File

@ -0,0 +1,27 @@
# Copyright (c) 2024 The AimRT Authors.
# AimRT is licensed under Mulan PSL v2.
aimrt:
plugin:
plugins:
- name: ros2_plugin
path: ${AIMRT_PLUGIN_DIR}/libaimrt_ros2_plugin.so
options:
node_name: example_ros2_rpc_server_node
executor_type: MultiThreaded # SingleThreaded/StaticSingleThreaded/MultiThreaded
executor_thread_num: 2
log:
core_lvl: Info # Trace/Debug/Info/Warn/Error/Fatal/Off
default_module_lvl: INFO
backends:
- type: console
rpc:
backends:
- type: ros2
servers_options:
- func_name: "(.*)"
enable_backends: [ros2]
module:
modules:
- name: NormalRpcServerPyModule
log_lvl: INFO

View File

@ -0,0 +1,9 @@
#!/bin/bash
export AIMRT_PLUGIN_DIR=$(pip show aimrt_py | grep Location | awk '{print $2}')/aimrt_py
export AMENT_CURRENT_PREFIX=${AIMRT_PLUGIN_DIR}
source ${AIMRT_PLUGIN_DIR}/share/example_ros2/local_setup.bash
source ${AIMRT_PLUGIN_DIR}/share/ros2_plugin_proto/local_setup.bash
gdb --args python3 ./examples_py_ros2_rpc_client_app.py --cfg_file_path=./cfg/examples_py_ros2_rpc_ros2_client_cfg.yaml

View File

@ -0,0 +1,71 @@
# Copyright (c) 2024 The AimRT Authors.
# AimRT is licensed under Mulan PSL v2.
import argparse
import datetime
import threading
import time
import aimrt_py
import example_ros2.srv
import RosTestRpc_aimrt_rpc_ros2
def main():
parser = argparse.ArgumentParser(description='Example rpc client app.')
parser.add_argument('--cfg_file_path', type=str, default="", help='config file path')
args = parser.parse_args()
print("AimRT start.")
aimrt_core = aimrt_py.Core()
# Initialize
core_options = aimrt_py.CoreOptions()
core_options.cfg_file_path = args.cfg_file_path
aimrt_core.Initialize(core_options)
# Create Module
module_handle = aimrt_core.CreateModule("NormalRpcClientPyModule")
# Register rpc client
rpc_handle = module_handle.GetRpcHandle()
ret = RosTestRpc_aimrt_rpc_ros2.RosTestRpcServiceProxy.RegisterClientFunc(rpc_handle)
assert ret, "Register client failed."
# Start
thread = threading.Thread(target=aimrt_core.Start)
thread.start()
# Sleep for seconds
time.sleep(1)
# Call rpc
proxy = RosTestRpc_aimrt_rpc_ros2.RosTestRpcServiceProxy(rpc_handle)
req = example_ros2.srv.RosTestRpc.Request()
req.data = [bytes([b]) for b in b"Hello AimRT!"]
ctx = aimrt_py.RpcContext()
ctx.SetTimeout(datetime.timedelta(seconds=30))
ctx.SetMetaValue("key1", "value1")
ctx.SetSerializationType("ros2")
ctx_ref = aimrt_py.RpcContextRef(ctx)
status, rsp = proxy.RosTestRpc(ctx_ref, req)
aimrt_py.info(module_handle.GetLogger(),
f"Call rpc done, status: {status.ToString()}, "
f"req: {req}, "
f"rsp: {rsp}")
# Shutdown
aimrt_core.Shutdown()
thread.join()
print("AimRT exit.")
if __name__ == '__main__':
main()

View File

@ -0,0 +1,92 @@
# Copyright (c) 2024 The AimRT Authors.
# AimRT is licensed under Mulan PSL v2.
import argparse
import signal
import sys
import threading
import aimrt_py
import example_ros2.msg
import example_ros2.srv
import RosTestRpc_aimrt_rpc_ros2
global_aimrt_core = None
def signal_handler(sig, frame):
global global_aimrt_core
if (global_aimrt_core and (sig == signal.SIGINT or sig == signal.SIGTERM)):
global_aimrt_core.Shutdown()
return
sys.exit(0)
class RosTestRpcImpl(RosTestRpc_aimrt_rpc_ros2.RosTestRpcService):
def __init__(self, logger):
super().__init__()
self.logger = logger
@staticmethod
def PrintMetaInfo(logger, ctx_ref):
meta_keys = ctx_ref.GetMetaKeys()
for key in meta_keys:
aimrt_py.info(logger, f"meta key: {key}, value: {ctx_ref.GetMetaValue(key)}")
def RosTestRpc(self, ctx_ref, req):
rsp = example_ros2.srv.RosTestRpc.Response()
rsp.code = 1000
RosTestRpcImpl.PrintMetaInfo(self.logger, ctx_ref)
aimrt_py.info(self.logger,
f"Server handle new rpc call. "
f"context: {ctx_ref.ToString()}, "
f"req: {req}, "
f"return rsp: {rsp}")
return aimrt_py.RpcStatus(), rsp
def main():
parser = argparse.ArgumentParser(description='Example rpc server app.')
parser.add_argument('--cfg_file_path', type=str, default="", help='config file path')
args = parser.parse_args()
signal.signal(signal.SIGINT, signal_handler)
signal.signal(signal.SIGTERM, signal_handler)
print("AimRT start.")
aimrt_core = aimrt_py.Core()
global global_aimrt_core
global_aimrt_core = aimrt_core
# Initialize
core_options = aimrt_py.CoreOptions()
core_options.cfg_file_path = args.cfg_file_path
aimrt_core.Initialize(core_options)
# Create Module
module_handle = aimrt_core.CreateModule("NormalRpcServerPyModule")
# Register rpc service
service = RosTestRpcImpl(module_handle.GetLogger())
ret = module_handle.GetRpcHandle().RegisterService(service)
assert ret, "Register service failed."
# Start
thread = threading.Thread(target=aimrt_core.Start)
thread.start()
while thread.is_alive():
thread.join(1.0)
print("AimRT exit.")
if __name__ == '__main__':
main()

View File

@ -0,0 +1,6 @@
#!/bin/bash
export AIMRT_PLUGIN_DIR=$(pip show aimrt_py | grep Location | awk '{print $2}')/aimrt_py
source $AIMRT_PLUGIN_DIR/share/example_ros2/local_setup.bash
python3 ./examples_py_ros2_rpc_client_app.py --cfg_file_path=./cfg/examples_py_ros2_rpc_grpc_client_cfg.yaml

View File

@ -0,0 +1,6 @@
#!/bin/bash
export AIMRT_PLUGIN_DIR=$(pip show aimrt_py | grep Location | awk '{print $2}')/aimrt_py
source $AIMRT_PLUGIN_DIR/share/example_ros2/local_setup.bash
python3 ./examples_py_ros2_rpc_server_app.py --cfg_file_path=./cfg/examples_py_ros2_rpc_grpc_server_cfg.yaml

View File

@ -0,0 +1,6 @@
#!/bin/bash
export AIMRT_PLUGIN_DIR=$(pip show aimrt_py | grep Location | awk '{print $2}')/aimrt_py
source $AIMRT_PLUGIN_DIR/share/example_ros2/local_setup.bash
python3 ./examples_py_ros2_rpc_client_app.py --cfg_file_path=./cfg/examples_py_ros2_rpc_http_client_cfg.yaml

View File

@ -0,0 +1,6 @@
#!/bin/bash
export AIMRT_PLUGIN_DIR=$(pip show aimrt_py | grep Location | awk '{print $2}')/aimrt_py
source $AIMRT_PLUGIN_DIR/share/example_ros2/local_setup.bash
python3 ./examples_py_ros2_rpc_server_app.py --cfg_file_path=./cfg/examples_py_ros2_rpc_http_server_cfg.yaml

View File

@ -0,0 +1,9 @@
#!/bin/bash
export AIMRT_PLUGIN_DIR=$(pip show aimrt_py | grep Location | awk '{print $2}')/aimrt_py
export AMENT_CURRENT_PREFIX=${AIMRT_PLUGIN_DIR}
source ${AIMRT_PLUGIN_DIR}/share/example_ros2/local_setup.bash
source ${AIMRT_PLUGIN_DIR}/share/ros2_plugin_proto/local_setup.bash
python3 ./examples_py_ros2_rpc_client_app.py --cfg_file_path=./cfg/examples_py_ros2_rpc_ros2_client_cfg.yaml

View File

@ -0,0 +1,9 @@
#!/bin/bash
export AIMRT_PLUGIN_DIR=$(pip show aimrt_py | grep Location | awk '{print $2}')/aimrt_py
export AMENT_CURRENT_PREFIX=${AIMRT_PLUGIN_DIR}
source ${AIMRT_PLUGIN_DIR}/share/example_ros2/local_setup.bash
source ${AIMRT_PLUGIN_DIR}/share/ros2_plugin_proto/local_setup.bash
python3 ./examples_py_ros2_rpc_server_app.py --cfg_file_path=./cfg/examples_py_ros2_rpc_ros2_server_cfg.yaml

View File

@ -97,7 +97,7 @@ const aimrt_type_support_base_t* GetRos2MessageTypeSupport() {
if (aimrt::util::ToStdStringView(serialization_type) == "ros2") {
if (buffer_array_view.len == 1) {
rcl_serialized_message_t serialized_msg = rmw_get_zero_initialized_serialized_message();
serialized_msg.buffer = const_cast<uint8_t*>(static_cast<const uint8_t*>(buffer_array_view.data[0].data)),
serialized_msg.buffer = const_cast<uint8_t*>(static_cast<const uint8_t*>(buffer_array_view.data[0].data));
serialized_msg.buffer_length = buffer_array_view.data[0].len;
serialized_msg.buffer_capacity = buffer_array_view.data[0].len;
rcl_ret_t ret = rmw_deserialize(&serialized_msg, ts_ptr, msg);
@ -120,7 +120,7 @@ const aimrt_type_support_base_t* GetRos2MessageTypeSupport() {
}
rcl_serialized_message_t serialized_msg = rmw_get_zero_initialized_serialized_message();
serialized_msg.buffer = buffer,
serialized_msg.buffer = buffer;
serialized_msg.buffer_length = total_size;
serialized_msg.buffer_capacity = total_size;
rcl_ret_t ret = rmw_deserialize(&serialized_msg, ts_ptr, msg);

View File

@ -13,8 +13,13 @@ set(CUR_TARGET_NAME ${CUR_SUPERIOR_NAMESPACE_UNDERLINE}_${CUR_DIR})
set(CUR_TARGET_ALIAS_NAME ${CUR_SUPERIOR_NAMESPACE}::${CUR_DIR})
# Set file collection
file(GLOB_RECURSE src ${CMAKE_CURRENT_SOURCE_DIR}/*.cc)
file(GLOB_RECURSE py_files ${CMAKE_CURRENT_SOURCE_DIR}/*.py)
file(GLOB_RECURSE src CONFIGURE_DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/*.cc)
file(GLOB_RECURSE py_files CONFIGURE_DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/*.py)
file(GLOB_RECURSE ros2_files CONFIGURE_DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/ros2_type_support_utils.cc)
if(NOT AIMRT_BUILD_WITH_ROS2)
list(REMOVE_ITEM src ${ros2_files})
endif()
# Add target
pybind11_add_module(${CUR_TARGET_NAME} SHARED)
@ -28,6 +33,12 @@ target_link_libraries(
${CUR_TARGET_NAME}
PRIVATE $<LINK_LIBRARY:WHOLE_ARCHIVE,aimrt::runtime::core>)
# Link ros2 interface if building with ros2
if(AIMRT_BUILD_WITH_ROS2)
target_link_libraries(${CUR_TARGET_NAME} PRIVATE aimrt::interface::aimrt_module_ros2_interface)
target_compile_definitions(${CUR_TARGET_NAME} PRIVATE AIMRT_BUILD_WITH_ROS2)
endif()
# Set misc of target
set_target_properties(${CUR_TARGET_NAME} PROPERTIES OUTPUT_NAME "aimrt_python_runtime")

View File

@ -1,6 +1,9 @@
# Copyright (c) 2023, AgiBot Inc.
# All rights reserved.
from .aimrt_py_chn import (GetPbMessageTypeName, GetRos2MessageTypeName,
Publish, RegisterPublishType, Subscribe)
from .aimrt_py_log import *
from .aimrt_py_pb_chn import Publish, RegisterPublishType, Subscribe
from .aimrt_python_runtime import *
from .check_ros2_type import (check_is_valid_ros2_msg_type,
check_is_valid_srv_type)

View File

@ -0,0 +1,206 @@
# Copyright (c) 2023, AgiBot Inc.
# All rights reserved.
import inspect
import sys
from typing import Callable, TypeVar
import google._upb._message
import google.protobuf
import google.protobuf.message
from . import aimrt_python_runtime
from .check_ros2_type import check_is_valid_ros2_msg_type
Ros2MsgType = TypeVar("Ros2MsgType")
def _SerializeProtobufMessage(pb_msg: google.protobuf.message.Message, serialization_type: str) -> bytes:
if serialization_type == "pb":
return pb_msg.SerializeToString()
elif serialization_type == "json":
return google.protobuf.json_format.MessageToJson(pb_msg)
else:
raise ValueError(f"Invalid serialization type: {serialization_type}")
def _DeserializeProtobufMessage(msg_buf: bytes,
serialization_type: str,
protobuf_type: google.protobuf.message.Message) -> google.protobuf.message.Message:
msg = protobuf_type()
if serialization_type == "pb":
msg.ParseFromString(msg_buf)
elif serialization_type == "json":
google.protobuf.json_format.Parse(msg_buf, msg)
else:
raise ValueError(f"Invalid serialization type: {serialization_type}")
return msg
def _CreateContextRef(ctx_or_type, default_serialization_type: str) -> aimrt_python_runtime.ContextRef:
if isinstance(ctx_or_type, aimrt_python_runtime.Context):
ctx_ref = aimrt_python_runtime.ContextRef(ctx_or_type)
elif isinstance(ctx_or_type, aimrt_python_runtime.ContextRef):
ctx_ref = ctx_or_type
else:
serialization_type = ctx_or_type if isinstance(ctx_or_type, str) else default_serialization_type
ctx = aimrt_python_runtime.Context()
ctx.SetSerializationType(serialization_type)
ctx_ref = aimrt_python_runtime.ContextRef(ctx)
if ctx_ref.GetSerializationType() == "":
ctx_ref.SetSerializationType(default_serialization_type)
return ctx_ref
def GetRos2MessageTypeName(msg_type: Ros2MsgType) -> str:
module_parts = msg_type.__module__.split('.')
module_name = '/'.join(module_parts[:-1])
return "ros2:" + "/".join([module_name, msg_type.__name__])
def GetPbMessageTypeName(msg: google._upb._message.MessageMeta) -> str:
return f"pb:{msg.DESCRIPTOR.full_name}"
def RegisterPublishType(publisher: aimrt_python_runtime.PublisherRef,
msg_type: google._upb._message.MessageMeta | Ros2MsgType):
"""Register a protobuf message type to a publisher.
Args:
publisher (aimrt_python_runtime.PublisherRef): channel publisher
msg_type (google.protobuf.message.Message | Ros2MsgType): protobuf message type or ROS2 message type
Returns:
bool: True if success, False otherwise
"""
if isinstance(msg_type, google._upb._message.MessageMeta):
py_pb_ts = aimrt_python_runtime.PyPbTypeSupport()
py_pb_ts.SetTypeName(GetPbMessageTypeName(msg_type))
py_pb_ts.SetSerializationTypesSupportedList(["pb", "json"])
return publisher.PbRegisterPublishType(py_pb_ts)
elif check_is_valid_ros2_msg_type(msg_type):
py_ros2_ts = aimrt_python_runtime.PyRos2TypeSupport(msg_type)
py_ros2_ts.SetTypeName(GetRos2MessageTypeName(msg_type))
py_ros2_ts.SetSerializationTypesSupportedList(["ros2"])
return publisher.Ros2RegisterPublishType(py_ros2_ts)
else:
raise TypeError(f"Invalid message type: {type(msg_type)}")
def Publish(publisher: aimrt_python_runtime.PublisherRef, second, third=None):
"""Publish a message to a channel.
This function can be called in following ways:
- Publish(publisher, msg)
- Publish(publisher, msg, ctx)
- Publish(publisher, msg, serialization_type)
- Publish(publisher, ctx, msg)
- Publish(publisher, serialization_type, msg)
msg: google.protobuf.message.Message | Ros2MsgType
ctx: aimrt_python_runtime.Context or aimrt_python_runtime.ContextRef
serialization_type: str
Args:
publisher (aimrt_python_runtime.PublisherRef): channel publisher
second: msg or ctx or serialization_type
third: msg or ctx or serialization_type or None
Raises:
TypeError: if the arguments are invalid
"""
if isinstance(second, google.protobuf.message.Message):
msg = second
ctx_or_type = third
message_type = "pb"
elif isinstance(third, google.protobuf.message.Message):
msg = third
ctx_or_type = second
message_type = "pb"
elif check_is_valid_ros2_msg_type(second):
msg = second
ctx_or_type = third
message_type = "ros2"
elif check_is_valid_ros2_msg_type(third):
msg = third
ctx_or_type = second
message_type = "ros2"
else:
raise TypeError("Invalid arguments, no protobuf message or ROS2 message found")
if not isinstance(ctx_or_type, (aimrt_python_runtime.Context, aimrt_python_runtime.ContextRef, str)) and \
ctx_or_type is not None:
raise TypeError(
f"Invalid context type: {type(ctx_or_type)}, "
f"only 'aimrt_python_runtime.Context' or 'aimrt_python_runtime.ContextRef' or 'str' is supported")
if message_type == "pb":
ctx_ref = _CreateContextRef(ctx_or_type, default_serialization_type="pb")
serialized_msg = _SerializeProtobufMessage(msg, ctx_ref.GetSerializationType())
publisher.PbPublishWithCtx(GetPbMessageTypeName(msg.__class__), ctx_ref, serialized_msg)
elif message_type == "ros2":
ctx_ref = _CreateContextRef(ctx_or_type, default_serialization_type="ros2")
publisher.Ros2PublishWithCtx(GetRos2MessageTypeName(msg.__class__), ctx_ref, msg)
def Subscribe(subscriber: aimrt_python_runtime.SubscriberRef,
msg_type: google._upb._message.MessageMeta | Ros2MsgType,
callback: Callable):
"""Subscribe a message from a channel.
Args:
subscriber (aimrt_python_runtime.SubscriberRef): channel subscriber
msg_type: protobuf message type or ROS2 message type
callback (Callable): callback function
Raises:
ValueError: if the callback is invalid
TypeError: if the message type is invalid
Callback function signature (both for Protobuf and ROS2 messages):
- callback(msg)
- callback(ctx, msg)
"""
# Check callback signature
sig = inspect.signature(callback)
required_param_count = sum(1 for param in sig.parameters.values() if param.default == param.empty)
if not (1 <= required_param_count <= 2):
raise ValueError("Invalid callback: expected 1 or 2 parameters, with at most one optional parameter")
if isinstance(msg_type, google._upb._message.MessageMeta):
py_pb_ts = aimrt_python_runtime.PyPbTypeSupport()
py_pb_ts.SetTypeName(GetPbMessageTypeName(msg_type))
py_pb_ts.SetSerializationTypesSupportedList(["pb", "json"])
def handle_callback(ctx_ref: aimrt_python_runtime.ContextRef, msg_buf: bytes):
try:
msg = _DeserializeProtobufMessage(msg_buf, ctx_ref.GetSerializationType(), msg_type)
if required_param_count == 1:
callback(msg)
else:
callback(ctx_ref, msg)
except Exception as e:
print(f"AimRT channel handle get exception, {e}", file=sys.stderr)
subscriber.PbSubscribeWithCtx(py_pb_ts, handle_callback)
elif check_is_valid_ros2_msg_type(msg_type):
py_ros2_ts = aimrt_python_runtime.PyRos2TypeSupport(msg_type)
py_ros2_ts.SetTypeName(GetRos2MessageTypeName(msg_type))
py_ros2_ts.SetSerializationTypesSupportedList(["ros2"])
def ros2_callback_wrapper(ctx_ref: aimrt_python_runtime.ContextRef, msg):
try:
if required_param_count == 1:
callback(msg)
else:
callback(ctx_ref, msg)
except Exception as e:
print(f"AimRT channel handle get exception, {e}", file=sys.stderr)
subscriber.Ros2SubscribeWithCtx(py_ros2_ts, msg_type, ros2_callback_wrapper)
else:
raise TypeError(f"Invalid message type: {type(msg_type)}, expected Protobuf or ROS2 message type")

View File

@ -1,142 +0,0 @@
# Copyright (c) 2023, AgiBot Inc.
# All rights reserved.
import inspect
from typing import Callable
import google.protobuf
import google.protobuf.message
from . import aimrt_python_runtime
def _SerializeProtobufMessage(pb_msg: google.protobuf.message.Message, serialization_type: str) -> bytes:
if serialization_type == "pb":
return pb_msg.SerializeToString()
elif serialization_type == "json":
return google.protobuf.json_format.MessageToJson(pb_msg)
else:
raise ValueError(f"Invalid serialization type: {serialization_type}")
def _DeserializeProtobufMessage(msg_buf: bytes,
serialization_type: str,
protobuf_type: google.protobuf.message.Message) -> google.protobuf.message.Message:
msg = protobuf_type()
if serialization_type == "pb":
msg.ParseFromString(msg_buf)
elif serialization_type == "json":
google.protobuf.json_format.Parse(msg_buf, msg)
else:
raise ValueError(f"Invalid serialization type: {serialization_type}")
return msg
def RegisterPublishType(publisher: aimrt_python_runtime.PublisherRef, protobuf_type: google.protobuf.message.Message):
"""Register a protobuf message type to a publisher.
Args:
publisher (aimrt_python_runtime.PublisherRef): channel publisher
protobuf_type (google.protobuf.message.Message): protobuf message type
Returns:
bool: True if success, False otherwise
"""
aimrt_ts = aimrt_python_runtime.TypeSupport()
aimrt_ts.SetTypeName("pb:" + protobuf_type.DESCRIPTOR.full_name)
aimrt_ts.SetSerializationTypesSupportedList(["pb", "json"])
return publisher.RegisterPublishType(aimrt_ts)
def Publish(publisher: aimrt_python_runtime.PublisherRef, second, third=None):
"""Publish a message to a channel.
This function can be called in following ways:
- Publish(publisher, pb_msg)
- Publish(publisher, pb_msg, ctx)
- Publish(publisher, pb_msg, serialization_type)
- Publish(publisher, ctx, pb_msg)
- Publish(publisher, serialization_type, pb_msg)
pb_msg: google.protobuf.message.Message
ctx: aimrt_python_runtime.Context or aimrt_python_runtime.ContextRef
serialization_type: str
Args:
publisher (aimrt_python_runtime.PublisherRef): channel publisher
second: pb_msg or ctx or serialization_type
third: pb_msg or ctx or serialization_type or None
Raises:
TypeError: if the arguments are invalid
"""
if isinstance(second, google.protobuf.message.Message):
pb_msg = second
ctx = third
elif isinstance(third, google.protobuf.message.Message):
pb_msg = third
ctx = second
else:
raise TypeError("Invalid arguments, no protobuf message found")
if isinstance(ctx, (aimrt_python_runtime.Context, aimrt_python_runtime.ContextRef)):
if isinstance(ctx, aimrt_python_runtime.Context):
ctx_ref = aimrt_python_runtime.ContextRef(ctx)
else:
ctx_ref = ctx
if ctx_ref.GetSerializationType() == "":
ctx_ref.SetSerializationType("pb")
serialized_msg = _SerializeProtobufMessage(pb_msg, ctx_ref.GetSerializationType())
publisher.PublishWithCtx(f"pb:{pb_msg.DESCRIPTOR.full_name}", ctx_ref, serialized_msg)
elif isinstance(ctx, str):
serialization_type = ctx
serialized_msg = _SerializeProtobufMessage(pb_msg, serialization_type)
publisher.PublishWithSerializationType(f"pb:{pb_msg.DESCRIPTOR.full_name}", serialization_type, serialized_msg)
elif ctx is None:
# default use pb serialization
serialized_msg = _SerializeProtobufMessage(pb_msg, "pb")
publisher.PublishWithSerializationType(f"pb:{pb_msg.DESCRIPTOR.full_name}", "pb", serialized_msg)
else:
raise TypeError(
f"Invalid context type: {type(ctx)}, "
f"only 'aimrt_python_runtime.Context' or 'aimrt_python_runtime.ContextRef' or 'str' is supported")
def Subscribe(subscriber: aimrt_python_runtime.SubscriberRef,
protobuf_type: google.protobuf.message.Message,
callback: Callable):
"""Subscribe a message from a channel.
Args:
subscriber (aimrt_python_runtime.SubscriberRef): channel subscriber
protobuf_type (google.protobuf.message.Message): protobuf message type
callback (Callable): callback function
Raises:
ValueError: if the callback is invalid
Callback function signature:
- callback(msg)
- callback(ctx, msg)
"""
aimrt_ts = aimrt_python_runtime.TypeSupport()
aimrt_ts.SetTypeName("pb:" + protobuf_type.DESCRIPTOR.full_name)
aimrt_ts.SetSerializationTypesSupportedList(["pb", "json"])
sig = inspect.signature(callback)
required_param_count = sum(1 for param in sig.parameters.values() if param.default == param.empty)
if not (1 <= required_param_count <= 2):
raise ValueError("Invalid callback: expected 1 or 2 parameters, with at most one optional parameter")
def handle_callback(ctx_ref: aimrt_python_runtime.ContextRef, msg_buf: bytes):
try:
msg = _DeserializeProtobufMessage(msg_buf, ctx_ref.GetSerializationType(), protobuf_type)
if required_param_count == 1:
callback(msg)
else:
callback(ctx_ref, msg)
except Exception as e:
print(f"AimRT channel handle get exception, {e}")
subscriber.SubscribeWithCtx(aimrt_ts, handle_callback)

View File

@ -0,0 +1,39 @@
# Copyright (c) 2024 The AimRT Authors.
# AimRT is licensed under Mulan PSL v2.
def check_for_ros2_type_support(msg_or_srv_type):
try:
ts = msg_or_srv_type.__class__._TYPE_SUPPORT
except AttributeError:
return False
if ts is None:
msg_or_srv_type.__class__.__import_type_support__()
return msg_or_srv_type.__class__._TYPE_SUPPORT is not None
def check_is_valid_ros2_msg_type(msg_type):
if not check_for_ros2_type_support(msg_type):
return False
try:
assert None not in (
msg_type.__class__._CREATE_ROS_MESSAGE,
msg_type.__class__._CONVERT_FROM_PY,
msg_type.__class__._CONVERT_TO_PY,
msg_type.__class__._DESTROY_ROS_MESSAGE,
)
except (AssertionError, AttributeError):
return False
return True
def check_is_valid_srv_type(srv_type):
check_for_ros2_type_support(srv_type)
try:
assert None not in (
srv_type.Response,
srv_type.Request,
)
except (AssertionError, AttributeError):
return False
return True

View File

@ -3,13 +3,19 @@
#pragma once
#include "pybind11/pybind11.h"
#include <utility>
#include "aimrt_module_cpp_interface/channel/channel_context.h"
#include "aimrt_module_cpp_interface/channel/channel_handle.h"
#include "python_runtime/export_type_support.h"
#include "pybind11/pybind11.h"
#include "python_runtime/export_pb_type_support.h"
#ifdef AIMRT_BUILD_WITH_ROS2
#include "python_runtime/export_ros2_type_support.h"
#include "python_runtime/ros2_type_support_utils.h"
#endif
namespace aimrt::runtime::python_runtime {
@ -36,9 +42,9 @@ inline void ExportContext(const pybind11::object& m) {
pybind11::class_<ContextRef>(m, "ContextRef")
.def(pybind11::init<>())
.def(pybind11::init<const Context&>())
.def(pybind11::init<const Context*>())
.def(pybind11::init<const std::shared_ptr<Context>&>())
.def(pybind11::init<const Context&>(), pybind11::keep_alive<1, 2>())
.def(pybind11::init<const Context*>(), pybind11::keep_alive<1, 2>())
.def(pybind11::init<const std::shared_ptr<Context>&>(), pybind11::keep_alive<1, 2>())
.def("__bool__", &ContextRef::operator bool)
.def("CheckUsed", &ContextRef::CheckUsed)
.def("SetUsed", &ContextRef::SetUsed)
@ -51,26 +57,16 @@ inline void ExportContext(const pybind11::object& m) {
.def("ToString", &ContextRef::ToString);
}
inline bool PyRegisterPublishType(
inline bool PbRegisterPublishType(
aimrt::channel::PublisherRef& publisher_ref,
const std::shared_ptr<const PyTypeSupport>& msg_type_support) {
static std::vector<std::shared_ptr<const PyTypeSupport>> py_ts_vec;
py_ts_vec.emplace_back(msg_type_support);
const std::shared_ptr<const PyPbTypeSupport>& msg_type_support) {
static std::vector<std::shared_ptr<const PyPbTypeSupport>> py_pb_ts_vec;
py_pb_ts_vec.emplace_back(msg_type_support);
return publisher_ref.RegisterPublishType(msg_type_support->NativeHandle());
}
inline void PyPublishWithSerializationType(
aimrt::channel::PublisherRef& publisher_ref,
std::string_view msg_type,
std::string_view serialization_type,
const std::string& msg_buf) {
aimrt::channel::Context ctx;
ctx.SetSerializationType(serialization_type);
publisher_ref.Publish(msg_type, ctx, static_cast<const void*>(&msg_buf));
}
inline void PyPublishWithCtx(
inline void PbPublishWithCtx(
aimrt::channel::PublisherRef& publisher_ref,
std::string_view msg_type,
const aimrt::channel::ContextRef& ctx_ref,
@ -78,24 +74,11 @@ inline void PyPublishWithCtx(
publisher_ref.Publish(msg_type, ctx_ref, static_cast<const void*>(&msg_buf));
}
inline void ExportPublisherRef(pybind11::object m) {
using aimrt::channel::PublisherRef;
pybind11::class_<PublisherRef>(std::move(m), "PublisherRef")
.def(pybind11::init<>())
.def("__bool__", &PublisherRef::operator bool)
.def("RegisterPublishType", &PyRegisterPublishType)
.def("PublishWithSerializationType", &PyPublishWithSerializationType)
.def("PublishWithCtx", &PyPublishWithCtx)
.def("GetTopic", &PublisherRef::GetTopic)
.def("MergeSubscribeContextToPublishContext", &PublisherRef::MergeSubscribeContextToPublishContext);
}
inline bool PySubscribeWithCtx(
inline bool PbSubscribeWithCtx(
aimrt::channel::SubscriberRef& subscriber_ref,
const std::shared_ptr<const PyTypeSupport>& msg_type_support,
const std::shared_ptr<const PyPbTypeSupport>& msg_type_support,
std::function<void(aimrt::channel::ContextRef, const pybind11::bytes&)>&& callback) {
static std::vector<std::shared_ptr<const PyTypeSupport>> py_ts_vec;
static std::vector<std::shared_ptr<const PyPbTypeSupport>> py_ts_vec;
py_ts_vec.emplace_back(msg_type_support);
return subscriber_ref.Subscribe(
@ -121,14 +104,105 @@ inline bool PySubscribeWithCtx(
});
}
#ifdef AIMRT_BUILD_WITH_ROS2
inline bool Ros2RegisterPublishType(
aimrt::channel::PublisherRef& publisher_ref,
const std::shared_ptr<const PyRos2TypeSupport>& py_ros2_type_support) {
static std::vector<std::shared_ptr<const PyRos2TypeSupport>> py_ros2_ts_vec;
py_ros2_ts_vec.emplace_back(py_ros2_type_support);
return publisher_ref.RegisterPublishType(py_ros2_type_support->NativeHandle());
}
inline void Ros2PublishWithCtx(
aimrt::channel::PublisherRef& publisher_ref,
std::string_view msg_type,
const aimrt::channel::ContextRef& ctx_ref,
pybind11::object msg_obj) {
auto msg_ptr = convert_from_py(msg_obj);
if (!msg_ptr) {
throw py::error_already_set();
}
publisher_ref.Publish(msg_type, ctx_ref, static_cast<const void*>(msg_ptr.get()));
}
inline bool Ros2SubscribeWithCtx(
aimrt::channel::SubscriberRef& subscriber_ref,
const std::shared_ptr<const PyRos2TypeSupport>& py_ros2_type_support,
pybind11::object pyclass,
std::function<void(aimrt::channel::ContextRef, pybind11::object)>&& callback) {
static std::vector<std::shared_ptr<const PyRos2TypeSupport>> py_ros2_ts_vec;
py_ros2_ts_vec.emplace_back(py_ros2_type_support);
pybind11::gil_scoped_acquire acquire;
pybind11::object pymetaclass = pyclass.attr("__class__");
auto* capsule_ptr = static_cast<void*>(pymetaclass.attr("_CONVERT_TO_PY").cast<py::capsule>());
typedef PyObject* convert_to_py_function(void*);
auto convert = reinterpret_cast<convert_to_py_function*>(capsule_ptr);
if (!convert) {
throw py::error_already_set();
}
pybind11::gil_scoped_release release;
return subscriber_ref.Subscribe(
py_ros2_type_support->NativeHandle(),
[callback = std::move(callback), convert](
const aimrt_channel_context_base_t* ctx_ptr,
const void* msg_ptr,
aimrt_function_base_t* release_callback_base) {
aimrt::channel::SubscriberReleaseCallback release_callback(release_callback_base);
pybind11::gil_scoped_acquire acquire;
auto msg_obj = pybind11::reinterpret_steal<pybind11::object>(convert(const_cast<void*>(msg_ptr)));
if (!msg_obj) {
throw py::error_already_set();
}
auto ctx_ref = aimrt::channel::ContextRef(ctx_ptr);
callback(ctx_ref, msg_obj);
pybind11::gil_scoped_release release;
release_callback();
});
}
#endif
inline void ExportPublisherRef(pybind11::object m) {
using aimrt::channel::PublisherRef;
pybind11::class_<PublisherRef>(std::move(m), "PublisherRef")
.def(pybind11::init<>())
.def("__bool__", &PublisherRef::operator bool)
.def("GetTopic", &PublisherRef::GetTopic)
.def("MergeSubscribeContextToPublishContext", &PublisherRef::MergeSubscribeContextToPublishContext)
.def("PbPublishWithCtx", &PbPublishWithCtx)
.def("PbRegisterPublishType", &PbRegisterPublishType)
#ifdef AIMRT_BUILD_WITH_ROS2
.def("Ros2PublishWithCtx", &Ros2PublishWithCtx)
.def("Ros2RegisterPublishType", &Ros2RegisterPublishType)
#endif
;
}
inline void ExportSubscriberRef(pybind11::object m) {
using aimrt::channel::SubscriberRef;
pybind11::class_<SubscriberRef>(std::move(m), "SubscriberRef")
.def(pybind11::init<>())
.def("__bool__", &SubscriberRef::operator bool)
.def("SubscribeWithCtx", &PySubscribeWithCtx)
.def("GetTopic", &SubscriberRef::GetTopic);
.def("GetTopic", &SubscriberRef::GetTopic)
.def("PbSubscribeWithCtx", &PbSubscribeWithCtx)
#ifdef AIMRT_BUILD_WITH_ROS2
.def("Ros2SubscribeWithCtx", &Ros2SubscribeWithCtx)
#endif
;
}
inline void ExportChannelHandleRef(pybind11::object m) {

View File

@ -3,12 +3,12 @@
#pragma once
#include "pybind11/pybind11.h"
#include <utility>
#include "aimrt_module_cpp_interface/configurator/configurator.h"
#include "pybind11/pybind11.h"
namespace aimrt::runtime::python_runtime {
inline void ExportConfiguratorRef(pybind11::object m) {

View File

@ -3,12 +3,12 @@
#pragma once
#include "pybind11/pybind11.h"
#include <utility>
#include "aimrt_module_cpp_interface/core.h"
#include "pybind11/pybind11.h"
namespace aimrt::runtime::python_runtime {
inline void ExportModuleInfo(pybind11::object m) {

View File

@ -3,13 +3,13 @@
#pragma once
#include "pybind11/pybind11.h"
#include <utility>
#include "aimrt_module_cpp_interface/module_base.h"
#include "core/aimrt_core.h"
#include "pybind11/pybind11.h"
namespace aimrt::runtime::python_runtime {
inline void ExportCoreOptions(pybind11::object m) {

View File

@ -3,13 +3,14 @@
#pragma once
#include "pybind11/pybind11.h"
#include <utility>
#include "aimrt_module_cpp_interface/executor/executor_manager.h"
#include "pybind11/chrono.h"
#include "pybind11/functional.h"
#include "pybind11/pybind11.h"
#include "pybind11/stl.h"
namespace aimrt::runtime::python_runtime {

View File

@ -3,12 +3,12 @@
#pragma once
#include "pybind11/pybind11.h"
#include <utility>
#include "aimrt_module_cpp_interface/logger/logger.h"
#include "pybind11/pybind11.h"
namespace aimrt::runtime::python_runtime {
inline void ExportLoggerRef(pybind11::object m) {

View File

@ -3,12 +3,12 @@
#pragma once
#include "pybind11/pybind11.h"
#include <utility>
#include "aimrt_module_cpp_interface/module_base.h"
#include "pybind11/pybind11.h"
namespace aimrt::runtime::python_runtime {
class PyModuleBaseAdapter : public ModuleBase {

View File

@ -3,10 +3,10 @@
#pragma once
#include "aimrt_module_cpp_interface/parameter/parameter_handle.h"
#include "pybind11/pybind11.h"
#include "aimrt_module_cpp_interface/parameter/parameter_handle.h"
namespace aimrt::runtime::python_runtime {
inline void ExportParameter(pybind11::object m) {
@ -19,4 +19,4 @@ inline void ExportParameter(pybind11::object m) {
.def("SetParameter", &ParameterHandleRef::SetParameter);
}
} // namespace aimrt::runtime::python_runtime
} // namespace aimrt::runtime::python_runtime

View File

@ -3,6 +3,8 @@
#pragma once
#include "pybind11/pybind11.h"
#include <cstring>
#include <utility>
@ -10,16 +12,15 @@
#include "aimrt_module_cpp_interface/util/string.h"
#include "pybind11/functional.h"
#include "pybind11/pybind11.h"
#include "pybind11/stl.h"
namespace aimrt::runtime::python_runtime {
class PyTypeSupport {
class PyPbTypeSupport {
public:
using BufType = std::string;
PyTypeSupport() : base_(GenBase(this)) {}
PyPbTypeSupport() : base_(GenBase(this)) {}
void SetTypeName(const std::string& s) {
type_name_ = s;
@ -97,31 +98,31 @@ class PyTypeSupport {
static aimrt_type_support_base_t GenBase(void* impl) {
return aimrt_type_support_base_t{
.type_name = [](void* impl) -> aimrt_string_view_t {
return aimrt::util::ToAimRTStringView(static_cast<PyTypeSupport*>(impl)->type_name_);
return aimrt::util::ToAimRTStringView(static_cast<PyPbTypeSupport*>(impl)->type_name_);
},
.create = [](void* impl) -> void* {
return static_cast<PyTypeSupport*>(impl)->Create();
return static_cast<PyPbTypeSupport*>(impl)->Create();
},
.destroy = [](void* impl, void* msg) {
static_cast<PyTypeSupport*>(impl)->Destroy(msg); //
static_cast<PyPbTypeSupport*>(impl)->Destroy(msg); //
},
.copy = [](void* impl, const void* from, void* to) {
static_cast<PyTypeSupport*>(impl)->Copy(from, to); //
static_cast<PyPbTypeSupport*>(impl)->Copy(from, to); //
},
.move = [](void* impl, void* from, void* to) {
static_cast<PyTypeSupport*>(impl)->Move(from, to); //
static_cast<PyPbTypeSupport*>(impl)->Move(from, to); //
},
.serialize = [](void* impl, aimrt_string_view_t serialization_type, const void* msg, const aimrt_buffer_array_allocator_t* allocator, aimrt_buffer_array_t* buffer_array) -> bool {
return static_cast<PyTypeSupport*>(impl)->Serialize(serialization_type, msg, allocator, buffer_array);
return static_cast<PyPbTypeSupport*>(impl)->Serialize(serialization_type, msg, allocator, buffer_array);
},
.deserialize = [](void* impl, aimrt_string_view_t serialization_type, aimrt_buffer_array_view_t buffer_array_view, void* msg) -> bool {
return static_cast<PyTypeSupport*>(impl)->Deserialize(serialization_type, buffer_array_view, msg);
return static_cast<PyPbTypeSupport*>(impl)->Deserialize(serialization_type, buffer_array_view, msg);
},
.serialization_types_supported_num = [](void* impl) -> size_t {
return static_cast<PyTypeSupport*>(impl)->serialization_types_supported_list_inner_.size();
return static_cast<PyPbTypeSupport*>(impl)->serialization_types_supported_list_inner_.size();
},
.serialization_types_supported_list = [](void* impl) -> const aimrt_string_view_t* {
return static_cast<PyTypeSupport*>(impl)->serialization_types_supported_list_inner_.data();
return static_cast<PyPbTypeSupport*>(impl)->serialization_types_supported_list_inner_.data();
},
.custom_type_support_ptr = [](void* impl) -> const void* {
return nullptr;
@ -136,11 +137,11 @@ class PyTypeSupport {
std::vector<aimrt_string_view_t> serialization_types_supported_list_inner_;
};
inline void ExportTypeSupport(pybind11::object m) {
pybind11::class_<PyTypeSupport, std::shared_ptr<PyTypeSupport>>(std::move(m), "TypeSupport")
inline void ExportPbTypeSupport(pybind11::object m) {
pybind11::class_<PyPbTypeSupport, std::shared_ptr<PyPbTypeSupport>>(std::move(m), "PyPbTypeSupport")
.def(pybind11::init<>())
.def("SetTypeName", &PyTypeSupport::SetTypeName)
.def("SetSerializationTypesSupportedList", &PyTypeSupport::SetSerializationTypesSupportedList);
.def("SetTypeName", &PyPbTypeSupport::SetTypeName)
.def("SetSerializationTypesSupportedList", &PyPbTypeSupport::SetSerializationTypesSupportedList);
}
} // namespace aimrt::runtime::python_runtime
} // namespace aimrt::runtime::python_runtime

View File

@ -0,0 +1,191 @@
// Copyright(c) 2024 The AimRT Authors.
// AimRT is licensed under Mulan PSL v2.
#pragma once
#include "pybind11/pybind11.h"
#include <memory>
#include "aimrt_module_c_interface/util/type_support_base.h"
#include "aimrt_module_cpp_interface/util/string.h"
#include "aimrt_module_ros2_interface/util/ros2_rcl_serialized_message_adapter.h"
#include "pybind11/functional.h"
#include "pybind11/stl.h"
#include "rosidl_runtime_cpp/message_type_support_decl.hpp"
#include "python_runtime/ros2_type_support_utils.h"
namespace aimrt::runtime::python_runtime {
namespace py = pybind11;
class PyRos2TypeSupport {
public:
explicit PyRos2TypeSupport(py::object pymsg_type) : base_(GenBase(this, pymsg_type)) {}
void SetTypeName(const std::string& s) {
type_name_ = s;
}
void SetSerializationTypesSupportedList(const std::vector<std::string>& v) {
serialization_types_supported_list_ = v;
serialization_types_supported_list_inner_.clear();
for (auto& item : serialization_types_supported_list_) {
serialization_types_supported_list_inner_.emplace_back(aimrt::util::ToAimRTStringView(item));
}
}
const aimrt_type_support_base_t* NativeHandle() const { return &base_; }
private:
void* Create() const { return create_ros_message_(); }
void Destroy(void* msg) const { destroy_ros_message_(msg); }
void Copy(const void* from, void* to) const {
const auto* members = GetRosMembersInfo(msg_type_support_);
CopyRosMessage(members, from, to);
}
void Move(void* from, void* to) const {
const auto* members = GetRosMembersInfo(msg_type_support_);
MoveRosMessage(members, from, to);
}
bool Serialize(
aimrt_string_view_t serialization_type,
const void* msg,
const aimrt_buffer_array_allocator_t* allocator,
aimrt_buffer_array_t* buffer_array) const {
try {
if (aimrt::util::ToStdStringView(serialization_type) == "ros2") {
aimrt_buffer_array_with_allocator_t bawa{.buffer_array = buffer_array, .allocator = allocator};
Ros2RclSerializedMessageAdapter serialized_msg_adapter(&bawa);
rcl_ret_t ret = rmw_serialize(msg, msg_type_support_, serialized_msg_adapter.GetRclSerializedMessage());
return (ret == RMW_RET_OK);
}
return false;
} catch (const std::exception& e) {
return false;
}
}
bool Deserialize(
aimrt_string_view_t serialization_type,
aimrt_buffer_array_view_t buffer_array_view,
void* msg) const {
try {
if (aimrt::util::ToStdStringView(serialization_type) == "ros2") {
if (buffer_array_view.len == 1) {
rcl_serialized_message_t serialized_msg = rmw_get_zero_initialized_serialized_message();
serialized_msg.buffer = const_cast<uint8_t*>(static_cast<const uint8_t*>(buffer_array_view.data[0].data));
serialized_msg.buffer_length = buffer_array_view.data[0].len;
serialized_msg.buffer_capacity = buffer_array_view.data[0].len;
rcl_ret_t ret = rmw_deserialize(&serialized_msg, msg_type_support_, msg);
return (ret == RMW_RET_OK);
}
if (buffer_array_view.len > 1) {
size_t total_size = 0;
for (size_t ii = 0; ii < buffer_array_view.len; ++ii) {
total_size += buffer_array_view.data[ii].len;
}
std::vector<uint8_t> buffer_vec(total_size);
uint8_t* buffer = buffer_vec.data();
size_t cur_size = 0;
for (size_t ii = 0; ii < buffer_array_view.len; ++ii) {
memcpy(buffer + cur_size,
buffer_array_view.data[ii].data,
buffer_array_view.data[ii].len);
cur_size += buffer_array_view.data[ii].len;
}
rcl_serialized_message_t serialized_msg = rmw_get_zero_initialized_serialized_message();
serialized_msg.buffer = buffer;
serialized_msg.buffer_length = total_size;
serialized_msg.buffer_capacity = total_size;
rcl_ret_t ret = rmw_deserialize(&serialized_msg, msg_type_support_, msg);
return (ret == RMW_RET_OK);
}
}
return false;
} catch (const std::exception& e) {
return false;
}
}
aimrt_type_support_base_t GenBase(PyRos2TypeSupport* impl, py::object pymsg_type) {
msg_type_support_ = static_cast<rosidl_message_type_support_t*>(common_get_type_support(pymsg_type));
py::object pymetaclass = pymsg_type.attr("__class__");
py::object value = pymetaclass.attr("_CREATE_ROS_MESSAGE");
auto* capsule_ptr = static_cast<void*>(value.cast<py::capsule>());
create_ros_message_ = reinterpret_cast<create_ros_message_function*>(capsule_ptr);
// TODO(zhangyi): throw exception in the constructor might not be a good idea.
if (!create_ros_message_) {
throw std::runtime_error("create_ros_message_ is nullptr");
}
value = pymetaclass.attr("_DESTROY_ROS_MESSAGE");
capsule_ptr = static_cast<void*>(value.cast<py::capsule>());
destroy_ros_message_ = reinterpret_cast<destroy_ros_message_function*>(capsule_ptr);
// TODO(zhangyi): throw exception in the constructor might not be a good idea.
if (!destroy_ros_message_) {
throw std::runtime_error("destroy_ros_message_ is nullptr");
}
return aimrt_type_support_base_t{
.type_name = [](void* impl) -> aimrt_string_view_t {
return aimrt::util::ToAimRTStringView(static_cast<PyRos2TypeSupport*>(impl)->type_name_);
},
.create = [](void* impl) -> void* {
return static_cast<PyRos2TypeSupport*>(impl)->Create();
},
.destroy = [](void* impl, void* msg) { //
static_cast<PyRos2TypeSupport*>(impl)->Destroy(msg);
},
.copy = [](void* impl, const void* from, void* to) { //
static_cast<PyRos2TypeSupport*>(impl)->Copy(from, to);
},
.move = [](void* impl, void* from, void* to) { //
static_cast<PyRos2TypeSupport*>(impl)->Move(from, to);
},
.serialize = [](void* impl, aimrt_string_view_t serialization_type, const void* msg, const aimrt_buffer_array_allocator_t* allocator, aimrt_buffer_array_t* buffer_array) -> bool {
return static_cast<PyRos2TypeSupport*>(impl)->Serialize(serialization_type, msg, allocator, buffer_array);
},
.deserialize = [](void* impl, aimrt_string_view_t serialization_type, aimrt_buffer_array_view_t buffer_array_view, void* msg) -> bool {
return static_cast<PyRos2TypeSupport*>(impl)->Deserialize(serialization_type, buffer_array_view, msg);
},
.serialization_types_supported_num = [](void* impl) -> size_t {
return static_cast<PyRos2TypeSupport*>(impl)->serialization_types_supported_list_inner_.size();
},
.serialization_types_supported_list = [](void* impl) -> const aimrt_string_view_t* {
return static_cast<PyRos2TypeSupport*>(impl)->serialization_types_supported_list_inner_.data();
},
.custom_type_support_ptr = [](void* impl) -> const void* {
return static_cast<PyRos2TypeSupport*>(impl)->msg_type_support_;
},
.impl = impl};
}
private:
rosidl_message_type_support_t* msg_type_support_;
create_ros_message_function* create_ros_message_;
destroy_ros_message_function* destroy_ros_message_;
aimrt_type_support_base_t base_;
std::string type_name_;
std::vector<std::string> serialization_types_supported_list_;
std::vector<aimrt_string_view_t> serialization_types_supported_list_inner_;
};
inline void ExportRos2TypeSupport(py::object m) {
py::class_<PyRos2TypeSupport, std::shared_ptr<PyRos2TypeSupport>>(std::move(m), "PyRos2TypeSupport")
.def(py::init<py::object>())
.def("SetTypeName", &PyRos2TypeSupport::SetTypeName)
.def("SetSerializationTypesSupportedList", &PyRos2TypeSupport::SetSerializationTypesSupportedList);
}
} // namespace aimrt::runtime::python_runtime

View File

@ -3,16 +3,21 @@
#pragma once
#include "pybind11/pybind11.h"
#include <future>
#include <string>
#include <utility>
#include "aimrt_module_cpp_interface/rpc/rpc_handle.h"
#include "python_runtime/export_type_support.h"
#include "pybind11/pybind11.h"
#include "python_runtime/export_pb_type_support.h"
#include "rpc/rpc_context_base.h"
#ifdef AIMRT_BUILD_WITH_ROS2
#include "python_runtime/export_ros2_type_support.h"
#include "python_runtime/ros2_type_support_utils.h"
#endif
namespace aimrt::runtime::python_runtime {
inline void ExportRpcStatus(const pybind11::object& m) {
@ -84,9 +89,9 @@ inline void ExportRpcContext(const pybind11::object& m) {
pybind11::class_<ContextRef>(m, "RpcContextRef")
.def(pybind11::init<>())
.def(pybind11::init<const Context&>())
.def(pybind11::init<Context*>())
.def(pybind11::init<const std::shared_ptr<Context>&>())
.def(pybind11::init<const Context&>(), pybind11::keep_alive<1, 2>())
.def(pybind11::init<Context*>(), pybind11::keep_alive<1, 2>())
.def(pybind11::init<const std::shared_ptr<Context>&>(), pybind11::keep_alive<1, 2>())
.def("__bool__", &ContextRef::operator bool)
.def("CheckUsed", &ContextRef::CheckUsed)
.def("SetUsed", &ContextRef::SetUsed)
@ -108,13 +113,13 @@ inline void ExportRpcContext(const pybind11::object& m) {
using ServiceFuncReturnType = std::tuple<aimrt::rpc::Status, std::string>;
using ServiceFuncType = std::function<ServiceFuncReturnType(aimrt::rpc::ContextRef, const pybind11::bytes&)>;
inline void PyRpcServiceBaseRegisterServiceFunc(
inline void PbRpcServiceBaseRegisterServiceFunc(
aimrt::rpc::ServiceBase& service,
std::string_view func_name,
const std::shared_ptr<const PyTypeSupport>& req_type_support,
const std::shared_ptr<const PyTypeSupport>& rsp_type_support,
const std::shared_ptr<const PyPbTypeSupport>& req_type_support,
const std::shared_ptr<const PyPbTypeSupport>& rsp_type_support,
ServiceFuncType&& service_func) {
static std::vector<std::shared_ptr<const PyTypeSupport>> py_ts_vec;
static std::vector<std::shared_ptr<const PyPbTypeSupport>> py_ts_vec;
py_ts_vec.emplace_back(req_type_support);
py_ts_vec.emplace_back(rsp_type_support);
@ -154,23 +159,12 @@ inline void PyRpcServiceBaseRegisterServiceFunc(
std::move(aimrt_service_func));
}
inline void ExportRpcServiceBase(pybind11::object m) {
using aimrt::rpc::ServiceBase;
pybind11::class_<ServiceBase>(std::move(m), "ServiceBase")
.def(pybind11::init<std::string_view, std::string_view>())
.def("RpcType", &ServiceBase::RpcType)
.def("ServiceName", &ServiceBase::ServiceName)
.def("SetServiceName", &ServiceBase::SetServiceName)
.def("RegisterServiceFunc", &PyRpcServiceBaseRegisterServiceFunc);
}
inline bool PyRpcHandleRefRegisterClientFunc(
inline bool PbRpcHandleRefRegisterClientFunc(
aimrt::rpc::RpcHandleRef& rpc_handle_ref,
std::string_view func_name,
const std::shared_ptr<const PyTypeSupport>& req_type_support,
const std::shared_ptr<const PyTypeSupport>& rsp_type_support) {
static std::vector<std::shared_ptr<const PyTypeSupport>> py_ts_vec;
const std::shared_ptr<const PyPbTypeSupport>& req_type_support,
const std::shared_ptr<const PyPbTypeSupport>& rsp_type_support) {
static std::vector<std::shared_ptr<const PyPbTypeSupport>> py_ts_vec;
py_ts_vec.emplace_back(req_type_support);
py_ts_vec.emplace_back(rsp_type_support);
@ -181,7 +175,7 @@ inline bool PyRpcHandleRefRegisterClientFunc(
rsp_type_support->NativeHandle());
}
inline std::tuple<aimrt::rpc::Status, pybind11::bytes> PyRpcHandleRefInvoke(
inline std::tuple<aimrt::rpc::Status, pybind11::bytes> PbRpcHandleRefInvoke(
aimrt::rpc::RpcHandleRef& rpc_handle_ref,
std::string_view func_name,
aimrt::rpc::ContextRef ctx_ref,
@ -191,10 +185,9 @@ inline std::tuple<aimrt::rpc::Status, pybind11::bytes> PyRpcHandleRefInvoke(
std::string rsp_buf;
std::promise<uint32_t> status_promise;
aimrt::rpc::ClientCallback callback(
[&status_promise](uint32_t status) {
status_promise.set_value(status);
});
aimrt::rpc::ClientCallback callback([&status_promise](uint32_t status) {
status_promise.set_value(status);
});
rpc_handle_ref.Invoke(
func_name,
@ -211,6 +204,150 @@ inline std::tuple<aimrt::rpc::Status, pybind11::bytes> PyRpcHandleRefInvoke(
return {aimrt::rpc::Status(fu.get()), pybind11::bytes(rsp_buf)};
}
#ifdef AIMRT_BUILD_WITH_ROS2
using Ros2ServiceFuncReturnType = std::tuple<aimrt::rpc::Status, pybind11::object>;
using Ros2ServiceFuncType = std::function<Ros2ServiceFuncReturnType(aimrt::rpc::ContextRef, pybind11::object)>;
inline void Ros2RpcServiceBaseRegisterServiceFunc(
aimrt::rpc::ServiceBase& service,
std::string_view func_name,
pybind11::object srv_pyclass,
const std::shared_ptr<const PyRos2TypeSupport>& req_type_support,
pybind11::object req_pyclass,
const std::shared_ptr<const PyRos2TypeSupport>& rsp_type_support,
pybind11::object rsp_pyclass,
Ros2ServiceFuncType&& service_func) {
static std::vector<std::shared_ptr<const PyRos2TypeSupport>> py_ts_vec;
py_ts_vec.emplace_back(req_type_support);
py_ts_vec.emplace_back(rsp_type_support);
pybind11::gil_scoped_acquire acquire;
auto req_convert_to_py = get_convert_to_py_function(req_pyclass);
auto rsp_convert_from_py = get_convert_from_py_function(rsp_pyclass);
void* srv_type_support = common_get_type_support(srv_pyclass);
pybind11::gil_scoped_release release;
aimrt::rpc::ServiceFunc aimrt_service_func(
[service_func{std::move(service_func)}, req_convert_to_py, rsp_convert_from_py](
const aimrt_rpc_context_base_t* ctx, const void* req_ptr, void* rsp_ptr, aimrt_function_base_t* callback) {
aimrt::rpc::ServiceCallback callback_f(callback);
aimrt::rpc::ContextRef ctx_ref(ctx);
try {
pybind11::gil_scoped_acquire acquire;
auto req_obj = pybind11::reinterpret_steal<pybind11::object>(req_convert_to_py(const_cast<void*>(req_ptr)));
if (!req_obj) {
throw py::error_already_set();
}
auto [status, rsp_obj] = service_func(ctx_ref, req_obj);
if (!rsp_convert_from_py(rsp_obj.ptr(), rsp_ptr)) {
throw py::error_already_set();
}
pybind11::gil_scoped_release release;
callback_f(status.Code());
return;
} catch (const std::exception& e) {
callback_f(AIMRT_RPC_STATUS_SVR_HANDLE_FAILED);
return;
}
});
service.RegisterServiceFunc(
func_name,
srv_type_support,
req_type_support->NativeHandle(),
rsp_type_support->NativeHandle(),
std::move(aimrt_service_func));
}
inline bool PyRos2RpcHandleRefRegisterClientFunc(
aimrt::rpc::RpcHandleRef& rpc_handle_ref,
std::string_view func_name,
pybind11::object srv_pyclass,
const std::shared_ptr<const PyRos2TypeSupport>& req_type_support,
const std::shared_ptr<const PyRos2TypeSupport>& rsp_type_support) {
static std::vector<std::shared_ptr<const PyRos2TypeSupport>> py_ts_vec;
py_ts_vec.emplace_back(req_type_support);
py_ts_vec.emplace_back(rsp_type_support);
pybind11::gil_scoped_acquire acquire;
void* srv_type_support = common_get_type_support(srv_pyclass);
pybind11::gil_scoped_release release;
return rpc_handle_ref.RegisterClientFunc(
func_name,
srv_type_support,
req_type_support->NativeHandle(),
rsp_type_support->NativeHandle());
}
inline std::tuple<aimrt::rpc::Status, pybind11::object> Ros2RpcHandleRefInvoke(
aimrt::rpc::RpcHandleRef& rpc_handle_ref,
std::string_view func_name,
aimrt::rpc::ContextRef ctx_ref,
pybind11::object req,
pybind11::object rsp_type) {
auto req_ptr = convert_from_py(req);
if (!req_ptr) {
throw py::error_already_set();
}
static auto rsp_create = get_create_ros_message_function(rsp_type);
auto* rsp_ptr = rsp_create();
pybind11::gil_scoped_release release;
std::promise<uint32_t> status_promise;
aimrt::rpc::ClientCallback callback([&status_promise](uint32_t status) {
status_promise.set_value(status);
});
rpc_handle_ref.Invoke(
func_name,
ctx_ref,
static_cast<const void*>(req_ptr.get()),
rsp_ptr,
std::move(callback));
auto fu = status_promise.get_future();
fu.wait();
pybind11::gil_scoped_acquire acquire;
static auto rsp_convert_to_py = get_convert_to_py_function(rsp_type);
auto rsp_obj = pybind11::reinterpret_steal<pybind11::object>(rsp_convert_to_py(rsp_ptr));
if (!rsp_obj) {
throw py::error_already_set();
}
return {aimrt::rpc::Status(fu.get()), rsp_obj};
}
#endif
inline void ExportRpcServiceBase(pybind11::object m) {
using aimrt::rpc::ServiceBase;
pybind11::class_<ServiceBase>(std::move(m), "ServiceBase")
.def(pybind11::init<std::string_view, std::string_view>())
.def("RpcType", &ServiceBase::RpcType)
.def("ServiceName", &ServiceBase::ServiceName)
.def("SetServiceName", &ServiceBase::SetServiceName)
.def("PbRegisterServiceFunc", &PbRpcServiceBaseRegisterServiceFunc)
#ifdef AIMRT_BUILD_WITH_ROS2
.def("Ros2RegisterServiceFunc", &Ros2RpcServiceBaseRegisterServiceFunc)
#endif
;
}
inline void ExportRpcHandleRef(pybind11::object m) {
using aimrt::rpc::RpcHandleRef;
@ -221,8 +358,13 @@ inline void ExportRpcHandleRef(pybind11::object m) {
pybind11::overload_cast<std::string_view, aimrt::rpc::ServiceBase*>(&RpcHandleRef::RegisterService))
.def("RegisterService",
pybind11::overload_cast<aimrt::rpc::ServiceBase*>(&RpcHandleRef::RegisterService))
.def("RegisterClientFunc", &PyRpcHandleRefRegisterClientFunc)
.def("Invoke", &PyRpcHandleRefInvoke);
.def("PbRegisterClientFunc", &PbRpcHandleRefRegisterClientFunc)
.def("PbInvoke", &PbRpcHandleRefInvoke)
#ifdef AIMRT_BUILD_WITH_ROS2
.def("Ros2RegisterClientFunc", &PyRos2RpcHandleRefRegisterClientFunc)
.def("Ros2Invoke", &Ros2RpcHandleRefInvoke)
#endif
;
}
inline void ExportRpcProxyBase(pybind11::object m) {

View File

@ -1,6 +1,8 @@
// Copyright (c) 2023, AgiBot Inc.
// All rights reserved.
#include "pybind11/pybind11.h"
#include "python_runtime/export_channel.h"
#include "python_runtime/export_configurator.h"
#include "python_runtime/export_core.h"
@ -9,10 +11,12 @@
#include "python_runtime/export_logger.h"
#include "python_runtime/export_module_base.h"
#include "python_runtime/export_parameter.h"
#include "python_runtime/export_pb_type_support.h"
#include "python_runtime/export_rpc.h"
#include "python_runtime/export_type_support.h"
#include "pybind11/pybind11.h"
#ifdef AIMRT_BUILD_WITH_ROS2
#include "python_runtime/export_ros2_type_support.h"
#endif
using namespace aimrt::runtime::python_runtime;
@ -24,7 +28,10 @@ PYBIND11_MODULE(aimrt_python_runtime, m) {
ExportCore(m);
// type support
ExportTypeSupport(m);
ExportPbTypeSupport(m);
#ifdef AIMRT_BUILD_WITH_ROS2
ExportRos2TypeSupport(m);
#endif
// core handle
ExportModuleInfo(m);

View File

@ -0,0 +1,557 @@
// Copyright(c) 2024 The AimRT Authors.
// AimRT is licensed under Mulan PSL v2.
#include "python_runtime/ros2_type_support_utils.h"
#include <cstddef>
#include "rcutils/allocator.h"
#include "rosidl_runtime_c/message_type_support_struct.h"
#include "rosidl_runtime_c/primitives_sequence.h" // IWYU pragma: keep
#include "rosidl_runtime_c/primitives_sequence_functions.h" // IWYU pragma: keep
#include "rosidl_runtime_c/string.h"
#include "rosidl_runtime_c/string_functions.h" // IWYU pragma: keep
#include "rosidl_runtime_c/u16string.h"
#include "rosidl_runtime_c/u16string_functions.h" // IWYU pragma: keep
#include "rosidl_typesupport_introspection_c/field_types.h"
#include "rosidl_typesupport_introspection_c/message_introspection.h"
namespace aimrt::runtime::python_runtime {
// NOLINTBEGIN(performance-unnecessary-value-param)
// The following code is adapted from ros2 rclpy.
// see: https://github.com/ros2/rclpy/blob/humble/rclpy/src/rclpy/utils.cpp
typedef void destroy_ros_message_function(void*);
typedef void* create_ros_message_function();
typedef bool convert_from_py_function(PyObject*, void*);
typedef PyObject* convert_to_py_function(void*);
void* common_get_type_support(py::object pymessage) {
py::object pymetaclass = pymessage.attr("__class__");
py::object value = pymetaclass.attr("_TYPE_SUPPORT");
auto* capsule_ptr = static_cast<void*>(value.cast<py::capsule>());
return capsule_ptr;
}
std::unique_ptr<void, destroy_ros_message_function*>
create_from_py(py::object pymessage) {
py::object pymetaclass = pymessage.attr("__class__");
py::object value = pymetaclass.attr("_CREATE_ROS_MESSAGE");
auto* capsule_ptr = static_cast<void*>(value.cast<py::capsule>());
auto create_ros_message =
reinterpret_cast<create_ros_message_function*>(capsule_ptr);
if (!create_ros_message) {
throw py::error_already_set();
}
value = pymetaclass.attr("_DESTROY_ROS_MESSAGE");
capsule_ptr = static_cast<void*>(value.cast<py::capsule>());
auto destroy_ros_message =
reinterpret_cast<destroy_ros_message_function*>(capsule_ptr);
if (!destroy_ros_message) {
throw py::error_already_set();
}
void* message = create_ros_message();
if (!message) {
throw std::bad_alloc();
}
return std::unique_ptr<
void, destroy_ros_message_function*>(message, destroy_ros_message);
}
std::unique_ptr<void, destroy_ros_message_function*>
convert_from_py(py::object pymessage) {
std::unique_ptr<void, destroy_ros_message_function*> message =
create_from_py(pymessage);
py::object pymetaclass = pymessage.attr("__class__");
auto* capsule_ptr = static_cast<void*>(
pymetaclass.attr("_CONVERT_FROM_PY").cast<py::capsule>());
auto convert =
reinterpret_cast<convert_from_py_function*>(capsule_ptr);
if (!convert) {
throw py::error_already_set();
}
if (!convert(pymessage.ptr(), message.get())) {
throw py::error_already_set();
}
return message;
}
create_ros_message_function* get_create_ros_message_function(py::object pyclass) {
py::object pymetaclass = pyclass.attr("__class__");
auto* capsule_ptr = static_cast<void*>(pymetaclass.attr("_CREATE_ROS_MESSAGE").cast<py::capsule>());
auto create = reinterpret_cast<create_ros_message_function*>(capsule_ptr);
if (!create) {
throw py::error_already_set();
}
return create;
}
destroy_ros_message_function* get_destroy_ros_message_function(py::object pyclass) {
py::object pymetaclass = pyclass.attr("__class__");
auto* capsule_ptr = static_cast<void*>(pymetaclass.attr("_DESTROY_ROS_MESSAGE").cast<py::capsule>());
auto destroy = reinterpret_cast<destroy_ros_message_function*>(capsule_ptr);
if (!destroy) {
throw py::error_already_set();
}
return destroy;
}
convert_to_py_function* get_convert_to_py_function(py::object pyclass) {
py::object pymetaclass = pyclass.attr("__class__");
auto* capsule_ptr = static_cast<void*>(pymetaclass.attr("_CONVERT_TO_PY").cast<py::capsule>());
auto convert = reinterpret_cast<convert_to_py_function*>(capsule_ptr);
if (!convert) {
throw py::error_already_set();
}
return convert;
}
convert_from_py_function* get_convert_from_py_function(py::object pyclass) {
py::object pymetaclass = pyclass.attr("__class__");
auto* capsule_ptr = static_cast<void*>(pymetaclass.attr("_CONVERT_FROM_PY").cast<py::capsule>());
auto convert = reinterpret_cast<convert_from_py_function*>(capsule_ptr);
if (!convert) {
throw py::error_already_set();
}
return convert;
}
// End of adapted code from ros2 rclpy.
// NOLINTEND(performance-unnecessary-value-param)
namespace {
template <enum rosidl_typesupport_introspection_c_field_types T>
struct RosTypeMapping;
#define ROS_TYPE_ID(ros_type) \
rosidl_typesupport_introspection_c__ROS_TYPE_##ros_type
#define ROS_SEQUENCE_TYPE(name_in_sequence) \
rosidl_runtime_c__##name_in_sequence##__Sequence
#define ROS_SEQUENCE_COPY_FUNCTION(name_in_sequence) \
rosidl_runtime_c__##name_in_sequence##__Sequence__copy
// There is no move function for sequence in ROS2.
// So we implement these functions in RosTypeMapping.
#define ROS_SEQUENCE_MOVE_FUNCTION(name_in_sequence) \
rosidl_runtime_c__##name_in_sequence##__Sequence__move
#define DEFINE_ROS_BASIC_TYPE_MAPPING(ros_type, name_in_sequence, c_type) \
template <> \
struct RosTypeMapping<ROS_TYPE_ID(ros_type)> { \
using CType = c_type; \
using SequenceType = ROS_SEQUENCE_TYPE(name_in_sequence); \
static constexpr auto SequenceCopyFunction = ROS_SEQUENCE_COPY_FUNCTION(name_in_sequence); \
static void SequenceMoveFunction(SequenceType* from_seq, SequenceType* to_seq) { \
auto allocator = rcutils_get_default_allocator(); \
allocator.deallocate(to_seq->data, allocator.state); \
*to_seq = *from_seq; \
memset(from_seq, 0, sizeof(SequenceType)); \
} \
};
#define ROS_STRING_COPY_FUNCTION(name_in_sequence) \
rosidl_runtime_c__##name_in_sequence##__copy
#define DEFINE_ROS_STRING_TYPE_MAPPING(ros_type, name_in_sequence, c_type) \
template <> \
struct RosTypeMapping<ROS_TYPE_ID(ros_type)> { \
using CType = c_type; \
using SequenceType = ROS_SEQUENCE_TYPE(name_in_sequence); \
static constexpr auto CopyFunction = ROS_STRING_COPY_FUNCTION(name_in_sequence); \
static constexpr auto SequenceCopyFunction = ROS_SEQUENCE_COPY_FUNCTION(name_in_sequence); \
static void MoveFunction(CType* from_str, CType* to_str) { \
auto allocator = rcutils_get_default_allocator(); \
allocator.deallocate(to_str->data, allocator.state); \
*to_str = *from_str; \
memset(from_str, 0, sizeof(CType)); \
} \
static void SequenceMoveFunction(SequenceType* from_seq, SequenceType* to_seq) { \
auto allocator = rcutils_get_default_allocator(); \
for (size_t ii = 0; ii < to_seq->capacity; ++ii) { \
allocator.deallocate(to_seq->data[ii].data, allocator.state); \
} \
allocator.deallocate(to_seq->data, allocator.state); \
*to_seq = *from_seq; \
memset(from_seq, 0, sizeof(SequenceType)); \
} \
};
DEFINE_ROS_BASIC_TYPE_MAPPING(FLOAT, float, float)
DEFINE_ROS_BASIC_TYPE_MAPPING(DOUBLE, double, double)
DEFINE_ROS_BASIC_TYPE_MAPPING(LONG_DOUBLE, long_double, long double)
DEFINE_ROS_BASIC_TYPE_MAPPING(CHAR, char, unsigned char)
DEFINE_ROS_BASIC_TYPE_MAPPING(WCHAR, wchar, char16_t)
DEFINE_ROS_BASIC_TYPE_MAPPING(BOOLEAN, boolean, bool)
DEFINE_ROS_BASIC_TYPE_MAPPING(OCTET, octet, std::byte)
DEFINE_ROS_BASIC_TYPE_MAPPING(UINT8, uint8, uint8_t)
DEFINE_ROS_BASIC_TYPE_MAPPING(INT8, int8, int8_t)
DEFINE_ROS_BASIC_TYPE_MAPPING(UINT16, uint16, uint16_t)
DEFINE_ROS_BASIC_TYPE_MAPPING(INT16, int16, int16_t)
DEFINE_ROS_BASIC_TYPE_MAPPING(UINT32, uint32, uint32_t)
DEFINE_ROS_BASIC_TYPE_MAPPING(INT32, int32, int32_t)
DEFINE_ROS_BASIC_TYPE_MAPPING(UINT64, uint64, uint64_t)
DEFINE_ROS_BASIC_TYPE_MAPPING(INT64, int64, int64_t)
DEFINE_ROS_STRING_TYPE_MAPPING(STRING, String, rosidl_runtime_c__String)
DEFINE_ROS_STRING_TYPE_MAPPING(WSTRING, U16String, rosidl_runtime_c__U16String)
#define COPY_ROS_BASIC_TYPE(ros_type) \
case ROS_TYPE_ID(ros_type): \
*reinterpret_cast<RosTypeMapping<ROS_TYPE_ID(ros_type)>::CType*>(to_ptr) = \
*reinterpret_cast<const RosTypeMapping<ROS_TYPE_ID(ros_type)>::CType*>(from_ptr); \
break;
#define COPY_ROS_STRING_TYPE(ros_type) \
case ROS_TYPE_ID(ros_type): \
RosTypeMapping<ROS_TYPE_ID(ros_type)>::CopyFunction( \
reinterpret_cast<const RosTypeMapping<ROS_TYPE_ID(ros_type)>::CType*>(from_ptr), \
reinterpret_cast<RosTypeMapping<ROS_TYPE_ID(ros_type)>::CType*>(to_ptr)); \
break;
template <typename CopyFunction>
concept introspection_c_copy_function = std::is_invocable_v<
CopyFunction,
const rosidl_typesupport_introspection_c__MessageMembers*, const void*, void*>;
template <typename MoveFunction>
concept introspection_c_move_function = std::is_invocable_v<
MoveFunction,
const rosidl_typesupport_introspection_c__MessageMembers*, void*, void*>;
template <typename CopyOrMoveFunction>
concept introspection_c_copy_or_move_function = introspection_c_copy_function<CopyOrMoveFunction> ||
introspection_c_move_function<CopyOrMoveFunction>;
template <introspection_c_copy_or_move_function CopyOrMoveFunction>
void HandleBasicMemberEmbeddedType(const rosidl_typesupport_introspection_c__MessageMember& member,
void* from_ptr, void* to_ptr,
CopyOrMoveFunction copy_or_move) {
const auto* sub_members =
reinterpret_cast<const rosidl_typesupport_introspection_c__MessageMembers*>(member.members_->data);
copy_or_move(sub_members, from_ptr, to_ptr);
}
void CopyBasicMember(
const rosidl_typesupport_introspection_c__MessageMember& member,
const void* from_ptr, void* to_ptr) {
switch (member.type_id_) {
COPY_ROS_BASIC_TYPE(FLOAT)
COPY_ROS_BASIC_TYPE(DOUBLE)
COPY_ROS_BASIC_TYPE(LONG_DOUBLE)
COPY_ROS_BASIC_TYPE(CHAR)
COPY_ROS_BASIC_TYPE(WCHAR)
COPY_ROS_BASIC_TYPE(BOOLEAN)
COPY_ROS_BASIC_TYPE(OCTET)
COPY_ROS_BASIC_TYPE(UINT8)
COPY_ROS_BASIC_TYPE(INT8)
COPY_ROS_BASIC_TYPE(UINT16)
COPY_ROS_BASIC_TYPE(INT16)
COPY_ROS_BASIC_TYPE(UINT32)
COPY_ROS_BASIC_TYPE(INT32)
COPY_ROS_BASIC_TYPE(UINT64)
COPY_ROS_BASIC_TYPE(INT64)
COPY_ROS_STRING_TYPE(STRING)
COPY_ROS_STRING_TYPE(WSTRING)
case rosidl_typesupport_introspection_c__ROS_TYPE_MESSAGE:
HandleBasicMemberEmbeddedType(member, const_cast<void*>(from_ptr), to_ptr, CopyRosMessage);
break;
default:
throw std::runtime_error("Unknown basic type: " + std::string(member.name_) +
" with type id: " + std::to_string(member.type_id_));
}
}
#define COPY_ROS_BASIC_TYPE_ARRAY(ros_type) \
case ROS_TYPE_ID(ros_type): \
std::memcpy(to_ptr, from_ptr, member.array_size_ * sizeof(RosTypeMapping<ROS_TYPE_ID(ros_type)>::CType)); \
break;
#define COPY_ROS_STRING_TYPE_ARRAY(ros_type) \
case ROS_TYPE_ID(ros_type): { \
auto* to_str = reinterpret_cast<RosTypeMapping<ROS_TYPE_ID(ros_type)>::CType*>(to_ptr); \
const auto* from_str = reinterpret_cast<const RosTypeMapping<ROS_TYPE_ID(ros_type)>::CType*>(from_ptr); \
for (size_t i = 0; i < member.array_size_; ++i) { \
RosTypeMapping<ROS_TYPE_ID(ros_type)>::CopyFunction(&from_str[i], &to_str[i]); \
} \
break; \
}
template <introspection_c_copy_or_move_function CopyOrMoveFunction>
void HandleStaticArrayEmbeddedType(const rosidl_typesupport_introspection_c__MessageMember& member,
void* from_ptr, void* to_ptr,
CopyOrMoveFunction copy_or_move) {
const auto* sub_members =
reinterpret_cast<const rosidl_typesupport_introspection_c__MessageMembers*>(member.members_->data);
if (!member.get_function) [[unlikely]] {
throw std::runtime_error("Failed to get get function for message: " + std::string(member.name_));
}
for (size_t ii = 0; ii < member.array_size_; ++ii) {
copy_or_move(sub_members, member.get_function(from_ptr, ii), member.get_function(to_ptr, ii));
}
}
void CopyStaticSizeArray(
const rosidl_typesupport_introspection_c__MessageMember& member,
const void* from_ptr, void* to_ptr) {
switch (member.type_id_) {
COPY_ROS_BASIC_TYPE_ARRAY(FLOAT)
COPY_ROS_BASIC_TYPE_ARRAY(DOUBLE)
COPY_ROS_BASIC_TYPE_ARRAY(LONG_DOUBLE)
COPY_ROS_BASIC_TYPE_ARRAY(CHAR)
COPY_ROS_BASIC_TYPE_ARRAY(WCHAR)
COPY_ROS_BASIC_TYPE_ARRAY(BOOLEAN)
COPY_ROS_BASIC_TYPE_ARRAY(OCTET)
COPY_ROS_BASIC_TYPE_ARRAY(UINT8)
COPY_ROS_BASIC_TYPE_ARRAY(INT8)
COPY_ROS_BASIC_TYPE_ARRAY(UINT16)
COPY_ROS_BASIC_TYPE_ARRAY(INT16)
COPY_ROS_BASIC_TYPE_ARRAY(UINT32)
COPY_ROS_BASIC_TYPE_ARRAY(INT32)
COPY_ROS_BASIC_TYPE_ARRAY(UINT64)
COPY_ROS_BASIC_TYPE_ARRAY(INT64)
COPY_ROS_STRING_TYPE_ARRAY(STRING)
COPY_ROS_STRING_TYPE_ARRAY(WSTRING)
case rosidl_typesupport_introspection_c__ROS_TYPE_MESSAGE:
HandleStaticArrayEmbeddedType(member, const_cast<void*>(from_ptr), to_ptr, CopyRosMessage);
break;
default:
throw std::runtime_error("Unknown array type: " + std::string(member.name_) +
" with type id: " + std::to_string(member.type_id_));
}
}
#define COPY_ROS_SEQUENCE(ros_type) \
case ROS_TYPE_ID(ros_type): { \
auto* to_seq = reinterpret_cast<RosTypeMapping<ROS_TYPE_ID(ros_type)>::SequenceType*>(to_ptr); \
const auto* from_seq = reinterpret_cast<const RosTypeMapping<ROS_TYPE_ID(ros_type)>::SequenceType*>(from_ptr); \
RosTypeMapping<ROS_TYPE_ID(ros_type)>::SequenceCopyFunction(from_seq, to_seq); \
break; \
}
template <introspection_c_copy_or_move_function CopyOrMoveFunction>
void HandleDynamicArrayEmbeddedType(const rosidl_typesupport_introspection_c__MessageMember& member,
void* from_ptr, void* to_ptr,
CopyOrMoveFunction copy_or_move) {
const auto* sub_members =
reinterpret_cast<const rosidl_typesupport_introspection_c__MessageMembers*>(member.members_->data);
if (!member.get_function) [[unlikely]] {
throw std::runtime_error("Failed to get get function for message: " + std::string(member.name_));
}
if (!member.resize_function) [[unlikely]] {
throw std::runtime_error("Failed to get resize function for message: " + std::string(member.name_));
}
if (!member.size_function) [[unlikely]] {
throw std::runtime_error("Failed to get size function for message: " + std::string(member.name_));
}
auto from_size = member.size_function(from_ptr);
member.resize_function(to_ptr, from_size);
for (size_t ii = 0; ii < from_size; ++ii) {
copy_or_move(sub_members,
member.get_function(from_ptr, ii),
member.get_function(to_ptr, ii));
}
}
void CopyDynamicSizeArray(
const rosidl_typesupport_introspection_c__MessageMember& member,
const void* from_ptr, void* to_ptr) {
switch (member.type_id_) {
COPY_ROS_SEQUENCE(FLOAT)
COPY_ROS_SEQUENCE(DOUBLE)
COPY_ROS_SEQUENCE(LONG_DOUBLE)
COPY_ROS_SEQUENCE(CHAR)
COPY_ROS_SEQUENCE(WCHAR)
COPY_ROS_SEQUENCE(BOOLEAN)
COPY_ROS_SEQUENCE(OCTET)
COPY_ROS_SEQUENCE(UINT8)
COPY_ROS_SEQUENCE(INT8)
COPY_ROS_SEQUENCE(UINT16)
COPY_ROS_SEQUENCE(INT16)
COPY_ROS_SEQUENCE(UINT32)
COPY_ROS_SEQUENCE(INT32)
COPY_ROS_SEQUENCE(UINT64)
COPY_ROS_SEQUENCE(INT64)
COPY_ROS_SEQUENCE(STRING)
COPY_ROS_SEQUENCE(WSTRING)
case rosidl_typesupport_introspection_c__ROS_TYPE_MESSAGE:
HandleDynamicArrayEmbeddedType(member, const_cast<void*>(from_ptr), to_ptr, CopyRosMessage);
break;
default:
throw std::runtime_error("Unknown array type: " + std::string(member.name_) +
" with type id: " + std::to_string(member.type_id_));
}
}
#define MOVE_ROS_STRING_TYPE(ros_type) \
case ROS_TYPE_ID(ros_type): \
RosTypeMapping<ROS_TYPE_ID(ros_type)>::MoveFunction( \
reinterpret_cast<RosTypeMapping<ROS_TYPE_ID(ros_type)>::CType*>(from_ptr), \
reinterpret_cast<RosTypeMapping<ROS_TYPE_ID(ros_type)>::CType*>(to_ptr)); \
break;
void MoveBasicMember(const rosidl_typesupport_introspection_c__MessageMember& member,
void* from_ptr, void* to_ptr) {
switch (member.type_id_) {
COPY_ROS_BASIC_TYPE(FLOAT)
COPY_ROS_BASIC_TYPE(DOUBLE)
COPY_ROS_BASIC_TYPE(LONG_DOUBLE)
COPY_ROS_BASIC_TYPE(CHAR)
COPY_ROS_BASIC_TYPE(WCHAR)
COPY_ROS_BASIC_TYPE(BOOLEAN)
COPY_ROS_BASIC_TYPE(OCTET)
COPY_ROS_BASIC_TYPE(UINT8)
COPY_ROS_BASIC_TYPE(INT8)
COPY_ROS_BASIC_TYPE(UINT16)
COPY_ROS_BASIC_TYPE(INT16)
COPY_ROS_BASIC_TYPE(UINT32)
COPY_ROS_BASIC_TYPE(INT32)
COPY_ROS_BASIC_TYPE(UINT64)
COPY_ROS_BASIC_TYPE(INT64)
MOVE_ROS_STRING_TYPE(STRING)
MOVE_ROS_STRING_TYPE(WSTRING)
case rosidl_typesupport_introspection_c__ROS_TYPE_MESSAGE: {
HandleBasicMemberEmbeddedType(member, from_ptr, to_ptr, MoveRosMessage);
break;
}
default:
throw std::runtime_error("Unknown basic type: " + std::string(member.name_) +
" with type id: " + std::to_string(member.type_id_));
}
}
#define MOVE_ROS_STRING_TYPE_ARRAY(ros_type) \
case ROS_TYPE_ID(ros_type): { \
auto* to_str = reinterpret_cast<RosTypeMapping<ROS_TYPE_ID(ros_type)>::CType*>(to_ptr); \
auto* from_str = reinterpret_cast<RosTypeMapping<ROS_TYPE_ID(ros_type)>::CType*>(from_ptr); \
for (size_t i = 0; i < member.array_size_; ++i) { \
RosTypeMapping<ROS_TYPE_ID(ros_type)>::MoveFunction(&from_str[i], &to_str[i]); \
} \
break; \
}
void MoveStaticSizeArray(const rosidl_typesupport_introspection_c__MessageMember& member,
void* from_ptr, void* to_ptr) {
switch (member.type_id_) {
COPY_ROS_BASIC_TYPE_ARRAY(FLOAT)
COPY_ROS_BASIC_TYPE_ARRAY(DOUBLE)
COPY_ROS_BASIC_TYPE_ARRAY(LONG_DOUBLE)
COPY_ROS_BASIC_TYPE_ARRAY(CHAR)
COPY_ROS_BASIC_TYPE_ARRAY(WCHAR)
COPY_ROS_BASIC_TYPE_ARRAY(BOOLEAN)
COPY_ROS_BASIC_TYPE_ARRAY(OCTET)
COPY_ROS_BASIC_TYPE_ARRAY(UINT8)
COPY_ROS_BASIC_TYPE_ARRAY(INT8)
COPY_ROS_BASIC_TYPE_ARRAY(UINT16)
COPY_ROS_BASIC_TYPE_ARRAY(INT16)
COPY_ROS_BASIC_TYPE_ARRAY(UINT32)
COPY_ROS_BASIC_TYPE_ARRAY(INT32)
COPY_ROS_BASIC_TYPE_ARRAY(UINT64)
COPY_ROS_BASIC_TYPE_ARRAY(INT64)
MOVE_ROS_STRING_TYPE_ARRAY(STRING)
MOVE_ROS_STRING_TYPE_ARRAY(WSTRING)
case rosidl_typesupport_introspection_c__ROS_TYPE_MESSAGE: {
HandleStaticArrayEmbeddedType(member, from_ptr, to_ptr, MoveRosMessage);
break;
}
default:
throw std::runtime_error("Unknown array type: " + std::string(member.name_) +
" with type id: " + std::to_string(member.type_id_));
}
}
#define MOVE_ROS_SEQUENCE(ros_type) \
case ROS_TYPE_ID(ros_type): { \
auto* to_seq = reinterpret_cast<RosTypeMapping<ROS_TYPE_ID(ros_type)>::SequenceType*>(to_ptr); \
auto* from_seq = reinterpret_cast<RosTypeMapping<ROS_TYPE_ID(ros_type)>::SequenceType*>(from_ptr); \
RosTypeMapping<ROS_TYPE_ID(ros_type)>::SequenceMoveFunction(from_seq, to_seq); \
break; \
}
void MoveDynamicSizeArray(const rosidl_typesupport_introspection_c__MessageMember& member,
void* from_ptr, void* to_ptr) {
switch (member.type_id_) {
MOVE_ROS_SEQUENCE(FLOAT)
MOVE_ROS_SEQUENCE(DOUBLE)
MOVE_ROS_SEQUENCE(LONG_DOUBLE)
MOVE_ROS_SEQUENCE(CHAR)
MOVE_ROS_SEQUENCE(WCHAR)
MOVE_ROS_SEQUENCE(BOOLEAN)
MOVE_ROS_SEQUENCE(OCTET)
MOVE_ROS_SEQUENCE(UINT8)
MOVE_ROS_SEQUENCE(INT8)
MOVE_ROS_SEQUENCE(UINT16)
MOVE_ROS_SEQUENCE(INT16)
MOVE_ROS_SEQUENCE(UINT32)
MOVE_ROS_SEQUENCE(INT32)
MOVE_ROS_SEQUENCE(UINT64)
MOVE_ROS_SEQUENCE(INT64)
MOVE_ROS_SEQUENCE(STRING)
MOVE_ROS_SEQUENCE(WSTRING)
case rosidl_typesupport_introspection_c__ROS_TYPE_MESSAGE: {
HandleDynamicArrayEmbeddedType(member, from_ptr, to_ptr, MoveRosMessage);
break;
}
default:
throw std::runtime_error("Unknown array type: " + std::string(member.name_) +
" with type id: " + std::to_string(member.type_id_));
}
}
} // namespace
const rosidl_typesupport_introspection_c__MessageMembers* GetRosMembersInfo(
const rosidl_message_type_support_t* type_support) {
const auto* ts = get_message_typesupport_handle(type_support, rosidl_typesupport_introspection_c__identifier);
if (!ts) [[unlikely]] {
throw std::runtime_error("Failed to get introspection type support.");
}
return reinterpret_cast<const rosidl_typesupport_introspection_c__MessageMembers*>(ts->data);
}
void CopyRosMessage(const rosidl_typesupport_introspection_c__MessageMembers* members,
const void* from, void* to) {
for (size_t ii = 0; ii < members->member_count_; ++ii) {
const auto& member = members->members_[ii];
const void* from_ptr = static_cast<const void*>(static_cast<const uint8_t*>(from) + member.offset_);
void* to_ptr = static_cast<void*>(static_cast<uint8_t*>(to) + member.offset_);
if (!member.is_array_) {
CopyBasicMember(member, from_ptr, to_ptr);
} else if (member.array_size_ > 0 && !member.is_upper_bound_) {
CopyStaticSizeArray(member, from_ptr, to_ptr);
} else {
CopyDynamicSizeArray(member, from_ptr, to_ptr);
}
}
}
void MoveRosMessage(const rosidl_typesupport_introspection_c__MessageMembers* members,
void* from, void* to) {
for (size_t ii = 0; ii < members->member_count_; ++ii) {
const auto& member = members->members_[ii];
void* from_ptr = static_cast<void*>(static_cast<uint8_t*>(from) + member.offset_);
void* to_ptr = static_cast<void*>(static_cast<uint8_t*>(to) + member.offset_);
if (!member.is_array_) {
MoveBasicMember(member, from_ptr, to_ptr);
} else if (member.array_size_ > 0 && !member.is_upper_bound_) {
MoveStaticSizeArray(member, from_ptr, to_ptr);
} else {
MoveDynamicSizeArray(member, from_ptr, to_ptr);
}
}
}
} // namespace aimrt::runtime::python_runtime

View File

@ -0,0 +1,46 @@
// Copyright(c) 2024 The AimRT Authors.
// AimRT is licensed under Mulan PSL v2.
#pragma once
#include "pybind11/pybind11.h" // IWYU pragma: keep
#include "rosidl_typesupport_introspection_c/identifier.h"
#include "rosidl_typesupport_introspection_c/message_introspection.h"
namespace aimrt::runtime::python_runtime {
namespace py = pybind11;
// The following code is adapted from ros2 rclpy.
// see: https://github.com/ros2/rclpy/blob/humble/rclpy/src/rclpy/utils.cpp
typedef void destroy_ros_message_function(void*);
typedef void* create_ros_message_function();
typedef bool convert_from_py_function(PyObject*, void*);
typedef PyObject* convert_to_py_function(void*);
void* common_get_type_support(py::object pymessage);
std::unique_ptr<void, destroy_ros_message_function*> create_from_py(py::object pymessage);
std::unique_ptr<void, destroy_ros_message_function*> convert_from_py(py::object pymessage);
create_ros_message_function* get_create_ros_message_function(py::object pyclass);
destroy_ros_message_function* get_destroy_ros_message_function(py::object pyclass);
convert_to_py_function* get_convert_to_py_function(py::object pyclass);
convert_from_py_function* get_convert_from_py_function(py::object pyclass);
// End of adapted code from ros2 rclpy.
const rosidl_typesupport_introspection_c__MessageMembers* GetRosMembersInfo(
const rosidl_message_type_support_t* type_support);
void CopyRosMessage(const rosidl_typesupport_introspection_c__MessageMembers* members, const void* from, void* to);
void MoveRosMessage(const rosidl_typesupport_introspection_c__MessageMembers* members, void* from, void* to);
} // namespace aimrt::runtime::python_runtime

View File

@ -2,8 +2,9 @@
# All rights reserved.
get_target_property(py_files aimrt::runtime::python_runtime PY_FILES)
file(GLOB_RECURSE gencode_file ${PROJECT_SOURCE_DIR}/src/tools/protoc_plugin_py_gen_aimrt_py_rpc/*.py)
list(APPEND py_files ${gencode_file})
file(GLOB_RECURSE gencode_file CONFIGURE_DEPENDS ${PROJECT_SOURCE_DIR}/src/tools/protoc_plugin_py_gen_aimrt_py_rpc/*.py)
file(GLOB_RECURSE ros2_gencode_file CONFIGURE_DEPENDS ${PROJECT_SOURCE_DIR}/src/tools/ros2_py_gen_aimrt_py_rpc/*.py)
list(APPEND py_files ${gencode_file} ${ros2_gencode_file})
set(AIMRT_PY_PKG_DIR ${CMAKE_BINARY_DIR}/aimrt_py_pkg)

View File

@ -22,3 +22,4 @@ dynamic = ["version"]
[project.scripts]
protoc_plugin_py_gen_aimrt_py_rpc = "aimrt_py.protoc_plugin_py_gen_aimrt_py_rpc:generate"
aimrt_py-gen-ros2-rpc = "aimrt_py.ros2_py_gen_aimrt_py_rpc:generate"

View File

@ -32,11 +32,11 @@ class {{service_name}}(aimrt_py.ServiceBase):
{{for method begin}}
# {{rpc_func_name}}
{{simple_rpc_req_name}}_aimrt_ts = aimrt_py.TypeSupport()
{{simple_rpc_req_name}}_aimrt_ts = aimrt_py.PyPbTypeSupport()
{{simple_rpc_req_name}}_aimrt_ts.SetTypeName("pb:" + {{full_rpc_req_py_name}}.DESCRIPTOR.full_name)
{{simple_rpc_req_name}}_aimrt_ts.SetSerializationTypesSupportedList(["pb", "json"])
{{simple_rpc_rsp_name}}_aimrt_ts = aimrt_py.TypeSupport()
{{simple_rpc_rsp_name}}_aimrt_ts = aimrt_py.PyPbTypeSupport()
{{simple_rpc_rsp_name}}_aimrt_ts.SetTypeName("pb:" + {{full_rpc_rsp_py_name}}.DESCRIPTOR.full_name)
{{simple_rpc_rsp_name}}_aimrt_ts.SetSerializationTypesSupportedList(["pb", "json"])
@ -72,7 +72,7 @@ class {{service_name}}(aimrt_py.ServiceBase):
return (st, rsp_str)
self.RegisterServiceFunc("{{rpc_func_name}}",
self.PbRegisterServiceFunc("{{rpc_func_name}}",
{{simple_rpc_req_name}}_aimrt_ts, {{simple_rpc_rsp_name}}_aimrt_ts, {{rpc_func_name}}AdapterFunc)
{{method end}}
@ -97,7 +97,7 @@ class {{service_name}}Proxy(aimrt_py.ProxyBase):
@overload
def {{rpc_func_name}}(
self, ctx_ref: aimrt_py.RpcContext, req: {{full_rpc_req_py_name}}
self, ctx: aimrt_py.RpcContext, req: {{full_rpc_req_py_name}}
) -> tuple[aimrt_py.RpcStatus, {{full_rpc_rsp_py_name}}]: ...
@overload
@ -145,7 +145,7 @@ class {{service_name}}Proxy(aimrt_py.ProxyBase):
except Exception as e:
return (aimrt_py.RpcStatus(aimrt_py.RpcStatusRetCode.CLI_SERIALIZATION_FAILED), rsp)
status, rsp_str = self.rpc_handle_ref.Invoke("pb:/{{package_name}}.{{service_name}}/{{rpc_func_name}}",
status, rsp_str = self.rpc_handle_ref.PbInvoke("pb:/{{package_name}}.{{service_name}}/{{rpc_func_name}}",
ctx_ref, req_str)
try:
@ -166,15 +166,15 @@ class {{service_name}}Proxy(aimrt_py.ProxyBase):
def RegisterClientFunc(rpc_handle):
{{for method begin}}
# {{rpc_func_name}}
{{simple_rpc_req_name}}_aimrt_ts = aimrt_py.TypeSupport()
{{simple_rpc_req_name}}_aimrt_ts = aimrt_py.PyPbTypeSupport()
{{simple_rpc_req_name}}_aimrt_ts.SetTypeName("pb:" + {{full_rpc_req_py_name}}.DESCRIPTOR.full_name)
{{simple_rpc_req_name}}_aimrt_ts.SetSerializationTypesSupportedList(["pb", "json"])
{{simple_rpc_rsp_name}}_aimrt_ts = aimrt_py.TypeSupport()
{{simple_rpc_rsp_name}}_aimrt_ts = aimrt_py.PyPbTypeSupport()
{{simple_rpc_rsp_name}}_aimrt_ts.SetTypeName("pb:" + {{full_rpc_rsp_py_name}}.DESCRIPTOR.full_name)
{{simple_rpc_rsp_name}}_aimrt_ts.SetSerializationTypesSupportedList(["pb", "json"])
ret = rpc_handle.RegisterClientFunc("pb:/{{package_name}}.{{service_name}}/{{rpc_func_name}}",
ret = rpc_handle.PbRegisterClientFunc("pb:/{{package_name}}.{{service_name}}/{{rpc_func_name}}",
{{simple_rpc_req_name}}_aimrt_ts, {{simple_rpc_rsp_name}}_aimrt_ts)
if(not ret):
return False

View File

@ -0,0 +1,151 @@
#! /usr/bin/env python3
# -*- coding: utf-8 -*-
# Copyright (c) 2024 The AimRT Authors.
# AimRT is licensed under Mulan PSL v2.
import os
import sys
t_pyfile: str = r"""# This file was generated by ros2-aimrt_py_rpc-gen-code-tool, do not edit it!!!
from typing import overload
import aimrt_py
import {{package_name}}.srv
if {{package_name}}.srv.{{service_name}}._TYPE_SUPPORT is None:
{{package_name}}.srv.{{service_name}}.__import_type_support__()
class {{service_name}}Service(aimrt_py.ServiceBase):
def __init__(self):
super().__init__("ros2", "{{package_name}}/srv")
# {{service_name}}
{{service_name}}_req_aimrt_ts = aimrt_py.PyRos2TypeSupport({{package_name}}.srv.{{service_name}}.Request)
{{service_name}}_req_aimrt_ts.SetTypeName(aimrt_py.GetRos2MessageTypeName({{package_name}}.srv.{{service_name}}.Request))
{{service_name}}_req_aimrt_ts.SetSerializationTypesSupportedList(["ros2"])
{{service_name}}_rsp_aimrt_ts = aimrt_py.PyRos2TypeSupport({{package_name}}.srv.{{service_name}}.Response)
{{service_name}}_rsp_aimrt_ts.SetTypeName(aimrt_py.GetRos2MessageTypeName({{package_name}}.srv.{{service_name}}.Response))
{{service_name}}_rsp_aimrt_ts.SetSerializationTypesSupportedList(["ros2"])
if not aimrt_py.check_is_valid_srv_type({{package_name}}.srv.{{service_name}}):
raise RuntimeError("The service type provided is not valid")
self.Ros2RegisterServiceFunc("{{service_name}}",
{{package_name}}.srv.{{service_name}},
{{service_name}}_req_aimrt_ts,
{{package_name}}.srv.{{service_name}}.Request,
{{service_name}}_rsp_aimrt_ts,
{{package_name}}.srv.{{service_name}}.Response,
self.{{service_name}})
def {{service_name}}(self, ctx_ref, req) -> tuple[aimrt_py.RpcStatus, {{package_name}}.srv.{{service_name}}.Response]:
return (aimrt_py.RpcStatus(aimrt_py.RpcStatusRetCode.SVR_NOT_IMPLEMENTED), {{package_name}}.srv.{{service_name}}.Response())
class {{service_name}}ServiceProxy(aimrt_py.ProxyBase):
def __init__(self, rpc_handle_ref=aimrt_py.RpcHandleRef()):
super().__init__(rpc_handle_ref, "ros2", "{{package_name}}")
self.rpc_handle_ref = rpc_handle_ref
@overload
def {{service_name}}(
self, req: {{package_name}}.srv.{{service_name}}.Request
) -> tuple[aimrt_py.RpcStatus, {{package_name}}.srv.{{service_name}}.Response]: ...
@overload
def {{service_name}}(
self, ctx: aimrt_py.RpcContext, req: {{package_name}}.srv.{{service_name}}.Request
) -> tuple[aimrt_py.RpcStatus, {{package_name}}.srv.{{service_name}}.Response]: ...
@overload
def {{service_name}}(
self, ctx_ref: aimrt_py.RpcContextRef, req: {{package_name}}.srv.{{service_name}}.Request
) -> tuple[aimrt_py.RpcStatus, {{package_name}}.srv.{{service_name}}.Response]: ...
def {{service_name}}(self, *args) -> tuple[aimrt_py.RpcStatus, {{package_name}}.srv.{{service_name}}.Response]:
if len(args) == 1:
ctx = super().NewContextSharedPtr()
req = args[0]
elif len(args) == 2:
ctx = args[0]
req = args[1]
else:
raise TypeError(f"{{service_name}} expects 1 or 2 arguments, got {len(args)}")
if isinstance(ctx, aimrt_py.RpcContext):
ctx_ref = aimrt_py.RpcContextRef(ctx)
elif isinstance(ctx, aimrt_py.RpcContextRef):
ctx_ref = ctx
else:
raise TypeError(f"ctx must be 'aimrt_py.RpcContext' or 'aimrt_py.RpcContextRef', got {type(ctx)}")
if ctx_ref:
if ctx_ref.GetSerializationType() == "":
ctx_ref.SetSerializationType("ros2")
else:
real_ctx = aimrt_py.RpcContext()
ctx_ref = aimrt_py.RpcContextRef(real_ctx)
ctx_ref.SetSerializationType("ros2")
status, rsp = self.rpc_handle_ref.Ros2Invoke(
"ros2:/{{package_name}}/srv/{{service_name}}", ctx_ref, req, {{package_name}}.srv.{{service_name}}.Response)
return status, rsp
@staticmethod
def RegisterClientFunc(rpc_handle):
# {{service_name}}
{{service_name}}_req_aimrt_ts = aimrt_py.PyRos2TypeSupport({{package_name}}.srv.{{service_name}}.Request)
{{service_name}}_req_aimrt_ts.SetTypeName(aimrt_py.GetRos2MessageTypeName({{package_name}}.srv.{{service_name}}.Request))
{{service_name}}_req_aimrt_ts.SetSerializationTypesSupportedList(["ros2"])
{{service_name}}_rsp_aimrt_ts = aimrt_py.PyRos2TypeSupport({{package_name}}.srv.{{service_name}}.Response)
{{service_name}}_rsp_aimrt_ts.SetTypeName(aimrt_py.GetRos2MessageTypeName({{package_name}}.srv.{{service_name}}.Response))
{{service_name}}_rsp_aimrt_ts.SetSerializationTypesSupportedList(["ros2"])
return rpc_handle.Ros2RegisterClientFunc("ros2:/{{package_name}}/srv/{{service_name}}",
{{package_name}}.srv.{{service_name}},
{{service_name}}_req_aimrt_ts,
{{service_name}}_rsp_aimrt_ts)
"""
def generate():
package_name = ""
service_file = ""
output_path = ""
for arg in sys.argv:
kv = arg.split('=')
if (kv[0] == '--pkg_name'):
package_name = kv[1]
elif (kv[0] == '--srv_file'):
service_file = kv[1]
elif (kv[0] == '--output_path'):
output_path = kv[1]
if package_name == "" or service_file == "" or output_path == "":
print(f"Usage: python3 ros2_py_gen_aimrt_py_rpc.py "
"--pkg_name=<package_name> "
"--srv_file=<service_file> "
"--output_path=<output_path>")
exit(1)
path, file = os.path.split(service_file)
filename, ext = os.path.splitext(file)
py_file_path = os.path.join(output_path, filename + "_aimrt_rpc_ros2.py")
# py file
f_py_file = open(py_file_path, 'w')
f_py_file.write(t_pyfile
.replace("{{package_name}}", package_name)
.replace("{{service_name}}", filename))
f_py_file.close()
if __name__ == '__main__':
generate()