diff --git a/orbbec_camera/CMakeLists.txt b/orbbec_camera/CMakeLists.txt index 3d64408e..b5ad08ac 100644 --- a/orbbec_camera/CMakeLists.txt +++ b/orbbec_camera/CMakeLists.txt @@ -269,6 +269,22 @@ orbbec_target_dependencies(${PROJECT_NAME} rclcpp_components_register_node( ${PROJECT_NAME} PLUGIN "orbbec_camera::OBCameraNodeDriver" EXECUTABLE orbbec_camera_node ) + +add_library(gige_action_command_component SHARED src/gige_action_command_node.cpp) +target_include_directories(gige_action_command_component PUBLIC ${COMMON_INCLUDE_DIRS}) +target_link_directories(gige_action_command_component PRIVATE ${ORBBEC_LIBS_DIR}) +target_link_libraries(gige_action_command_component + ${orbbec_camera_msgs_TARGETS} + OrbbecSDK + rclcpp::rclcpp +) +orbbec_target_dependencies(gige_action_command_component rclcpp_components) +rclcpp_components_register_node( + gige_action_command_component + PLUGIN "orbbec_camera::GigEActionCommandNode" + EXECUTABLE gige_action_command_node +) + # Add nodes using the macro add_orbbec_executable(list_devices_node tools/list_devices_node.cpp) add_orbbec_executable(list_depth_work_mode_node tools/list_depth_work_mode.cpp) @@ -360,7 +376,8 @@ rclcpp_components_register_node( ) # Install rules -install(TARGETS ${PROJECT_NAME} frame_latency start_benchmark multi_save_rgbir ARCHIVE DESTINATION lib +install(TARGETS ${PROJECT_NAME} gige_action_command_component frame_latency start_benchmark + multi_save_rgbir ARCHIVE DESTINATION lib LIBRARY DESTINATION lib RUNTIME DESTINATION bin ) diff --git a/orbbec_camera/examples/README.MD b/orbbec_camera/examples/README.MD index f598b6f7..92ea0115 100644 --- a/orbbec_camera/examples/README.MD +++ b/orbbec_camera/examples/README.MD @@ -12,6 +12,7 @@ For command-line maintenance and diagnostic utilities, see the official | Source | Purpose | Experience Level | Official Guide | | :---: | --- | :---: | --- | | [Net camera](./net_camera) | Launch supported Orbbec network cameras. | ⭐️ | [Network camera guide](https://orbbec.github.io/OrbbecSDK_ROS2/en/source/camera_devices/5_advanced_guide/configuration/net_camera.html) | +| [GigE Action Command](./gige_action_command) | Configure and trigger a Gemini 335Le camera group over GVCP. | ⭐️⭐️ | [README](./gige_action_command/README.md) | | [GMSL camera](./gmsl_camera) | Launch single, multiple, or synchronized GMSL cameras. | ⭐️ | [GMSL camera guide](https://orbbec.github.io/OrbbecSDK_ROS2/en/source/camera_devices/5_advanced_guide/multi_camera/gmsl_camera.html) | | [AE/AWB lock test](./ae_awb_lock) | Verify AE/AWB convergence, frame-metadata capture, and manual lock-in through ROS 2. | ⭐️⭐️ | [README](./ae_awb_lock/README.md) | | [Benchmark](./benchmark) | Benchmark the performance of different camera configurations. | ⭐️⭐️ | [Performance benchmark tools](https://orbbec.github.io/OrbbecSDK_ROS2/en/source/camera_devices/6_benchmark/benchmark_tools.html) | diff --git a/orbbec_camera/examples/gige_action_command/README.md b/orbbec_camera/examples/gige_action_command/README.md new file mode 100644 index 00000000..a2663fef --- /dev/null +++ b/orbbec_camera/examples/gige_action_command/README.md @@ -0,0 +1,68 @@ +# GigE Action Command + +This example starts two Gemini 335Le cameras in Group Actions synchronization mode and one +host-side Action Command sender. The sender is intentionally created once at the top level because +a GVCP Action Command can trigger multiple cameras. + +## Requirements + +- Gemini 335Le firmware 1.8.24 or later +- Orbbec SDK 2.10.2 or later +- Both cameras and the host on the same network + +Before running the example, change the two `net_device_ip` values in +`multi_gige_action_command.launch.py` to match the cameras. + +## Start the cameras and sender + +```bash +ros2 launch orbbec_camera multi_gige_action_command.launch.py +``` + +The launch file creates these device-scoped configuration services and one network-scoped sender: + +```text +/camera_01/get_action_config +/camera_01/set_action_config +/camera_02/get_action_config +/camera_02/set_action_config +/gige_action_command_node/send_action_command +``` + +## Configure the cameras + +Configure Action Signal block 0 on both cameras with matching keys and masks: + +```bash +ros2 service call /camera_01/set_action_config \ + orbbec_camera_msgs/srv/SetActionConfig \ + "{device_key: 1, selector: 0, group_key: 1, group_mask: 1}" + +ros2 service call /camera_02/set_action_config \ + orbbec_camera_msgs/srv/SetActionConfig \ + "{device_key: 1, selector: 0, group_key: 1, group_mask: 1}" +``` + +Read the configuration back when needed: + +```bash +ros2 service call /camera_01/get_action_config \ + orbbec_camera_msgs/srv/GetActionConfig \ + "{selector: 0}" +``` + +## Trigger the group + +Send an immediate broadcast command. Every camera whose device key, group key, and group mask +match the request will be triggered: + +```bash +ros2 service call /gige_action_command_node/send_action_command \ + orbbec_camera_msgs/srv/SendActionCommand \ + "{device_key: 1, group_key: 1, group_mask: 1, destination_ip: '255.255.255.255', scheduled_time: 0}" +``` + +`success: true` means the host dispatched the GVCP command; the protocol does not return a device +acknowledgment. A nonzero `scheduled_time` uses a GVCP/PTP timestamp, with seconds in the upper +32 bits and nanoseconds in the lower 32 bits. Scheduled triggering requires the cameras and sender +host to use synchronized time. diff --git a/orbbec_camera/examples/gige_action_command/multi_gige_action_command.launch.py b/orbbec_camera/examples/gige_action_command/multi_gige_action_command.launch.py new file mode 100644 index 00000000..3efeb0bf --- /dev/null +++ b/orbbec_camera/examples/gige_action_command/multi_gige_action_command.launch.py @@ -0,0 +1,53 @@ +import os + +from ament_index_python.packages import get_package_share_directory +from launch import LaunchDescription +from launch.actions import GroupAction, IncludeLaunchDescription, TimerAction +from launch.launch_description_sources import PythonLaunchDescriptionSource +from launch_ros.actions import Node + + +def generate_launch_description(): + package_dir = get_package_share_directory("orbbec_camera") + camera_launch = os.path.join( + package_dir, "launch", "gemini_330_series.launch.py" + ) + + camera_01 = IncludeLaunchDescription( + PythonLaunchDescriptionSource(camera_launch), + launch_arguments={ + "camera_name": "camera_01", + "enumerate_net_device": "false", + "net_device_ip": "192.168.1.10", + "net_device_port": "8090", + "sync_mode": "group_actions", + "log_file_name": "camera_01.log", + }.items(), + ) + + camera_02 = IncludeLaunchDescription( + PythonLaunchDescriptionSource(camera_launch), + launch_arguments={ + "camera_name": "camera_02", + "enumerate_net_device": "false", + "net_device_ip": "192.168.1.11", + "net_device_port": "8090", + "sync_mode": "group_actions", + "log_file_name": "camera_02.log", + }.items(), + ) + + action_command_node = Node( + package="orbbec_camera", + executable="gige_action_command_node", + name="gige_action_command_node", + output="screen", + ) + + return LaunchDescription( + [ + action_command_node, + TimerAction(period=0.0, actions=[GroupAction([camera_01])]), + TimerAction(period=2.0, actions=[GroupAction([camera_02])]), + ] + ) diff --git a/orbbec_camera/include/orbbec_camera/gige_action_command_node.h b/orbbec_camera/include/orbbec_camera/gige_action_command_node.h new file mode 100644 index 00000000..0e311238 --- /dev/null +++ b/orbbec_camera/include/orbbec_camera/gige_action_command_node.h @@ -0,0 +1,42 @@ +/******************************************************************************* + * Copyright (c) 2026 Orbbec 3D Technology, Inc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *******************************************************************************/ + +#pragma once + +#include + +#include + +#include "libobsensor/hpp/Context.hpp" +#include "orbbec_camera_msgs/srv/send_action_command.hpp" + +namespace orbbec_camera { + +class GigEActionCommandNode : public rclcpp::Node { + public: + explicit GigEActionCommandNode(const rclcpp::NodeOptions& node_options = rclcpp::NodeOptions()); + + private: + void sendActionCommandCallback( + const std::shared_ptr request, + std::shared_ptr response); + + std::unique_ptr context_; + rclcpp::Service::SharedPtr + send_action_command_service_; +}; + +} // namespace orbbec_camera diff --git a/orbbec_camera/include/orbbec_camera/ob_camera_node.h b/orbbec_camera/include/orbbec_camera/ob_camera_node.h index 30427d15..0e5a067d 100644 --- a/orbbec_camera/include/orbbec_camera/ob_camera_node.h +++ b/orbbec_camera/include/orbbec_camera/ob_camera_node.h @@ -53,6 +53,7 @@ #include "orbbec_camera_msgs/msg/depth_filter_state.hpp" #include "orbbec_camera_msgs/msg/depth_filters_status.hpp" #include "orbbec_camera_msgs/srv/get_device_config.hpp" +#include "orbbec_camera_msgs/srv/get_action_config.hpp" #include "orbbec_camera_msgs/srv/get_device_info.hpp" #include "orbbec_camera_msgs/srv/get_awb_gain.hpp" #include "orbbec_camera_msgs/msg/extrinsics.hpp" @@ -65,6 +66,7 @@ #include "orbbec_camera_msgs/srv/get_bool.hpp" #include "orbbec_camera_msgs/srv/set_string.hpp" #include "orbbec_camera_msgs/srv/set_filter.hpp" +#include "orbbec_camera_msgs/srv/set_action_config.hpp" #include "orbbec_camera_msgs/srv/set_arrays.hpp" #include "orbbec_camera_msgs/srv/set_stream_profile.hpp" #include "orbbec_camera_msgs/srv/get_user_calib_params.hpp" @@ -135,6 +137,7 @@ namespace orbbec_camera { using GetDeviceConfig = orbbec_camera_msgs::srv::GetDeviceConfig; +using GetActionConfig = orbbec_camera_msgs::srv::GetActionConfig; using GetDeviceInfo = orbbec_camera_msgs::srv::GetDeviceInfo; using Extrinsics = orbbec_camera_msgs::msg::Extrinsics; using SetInt32 = orbbec_camera_msgs::srv::SetInt32; @@ -146,6 +149,7 @@ using SetString = orbbec_camera_msgs::srv::SetString; using SetBool = std_srvs::srv::SetBool; using GetBool = orbbec_camera_msgs::srv::GetBool; using SetFilter = orbbec_camera_msgs::srv::SetFilter; +using SetActionConfig = orbbec_camera_msgs::srv::SetActionConfig; using SetArrays = orbbec_camera_msgs::srv::SetArrays; using SetStreamProfile = orbbec_camera_msgs::srv::SetStreamProfile; using SetUserCalibParams = orbbec_camera_msgs::srv::SetUserCalibParams; @@ -481,6 +485,12 @@ class OBCameraNode { void getDeviceConfigCallback(const std::shared_ptr& request, std::shared_ptr& response); + void getActionConfigCallback(const std::shared_ptr& request, + std::shared_ptr& response); + + void setActionConfigCallback(const std::shared_ptr& request, + std::shared_ptr& response); + void getSDKVersion(const std::shared_ptr& request, std::shared_ptr& response); @@ -778,6 +788,8 @@ class OBCameraNode { std::map::SharedPtr> set_ae_roi_srv_; rclcpp::Service::SharedPtr get_device_srv_; rclcpp::Service::SharedPtr get_device_config_srv_; + rclcpp::Service::SharedPtr get_action_config_srv_; + rclcpp::Service::SharedPtr set_action_config_srv_; rclcpp::Service::SharedPtr set_laser_enable_srv_; rclcpp::Service::SharedPtr set_ldp_enable_srv_; rclcpp::Service::SharedPtr get_ldp_status_srv_; diff --git a/orbbec_camera/src/gige_action_command_node.cpp b/orbbec_camera/src/gige_action_command_node.cpp new file mode 100644 index 00000000..f1e201b5 --- /dev/null +++ b/orbbec_camera/src/gige_action_command_node.cpp @@ -0,0 +1,78 @@ +/******************************************************************************* + * Copyright (c) 2026 Orbbec 3D Technology, Inc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *******************************************************************************/ + +#include "orbbec_camera/gige_action_command_node.h" + +#include +#include +#include +#include + +#include "rclcpp_components/register_node_macro.hpp" + +namespace orbbec_camera { +namespace { + +std::string formatObError(const ob::Error& error) { + std::ostringstream stream; + stream << (error.getMessage() ? error.getMessage() : "Unknown OB error") + << " status:" << static_cast(error.getStatus()); + return stream.str(); +} + +} // namespace + +GigEActionCommandNode::GigEActionCommandNode(const rclcpp::NodeOptions& node_options) + : Node("gige_action_command_node", node_options), context_(std::make_unique()) { + context_->enableNetDeviceEnumeration(true); + send_action_command_service_ = create_service( + "~/send_action_command", std::bind(&GigEActionCommandNode::sendActionCommandCallback, this, + std::placeholders::_1, std::placeholders::_2)); + RCLCPP_INFO(get_logger(), "GigE Action Command service is ready"); +} + +void GigEActionCommandNode::sendActionCommandCallback( + const std::shared_ptr request, + std::shared_ptr response) { + if (!request) { + response->success = false; + response->message = "Invalid request"; + return; + } + + const std::string destination_ip = + request->destination_ip.empty() ? "255.255.255.255" : request->destination_ip; + try { + response->success = + context_->sendActionCommand(request->device_key, request->group_key, request->group_mask, + destination_ip.c_str(), request->scheduled_time); + response->message = + response->success ? "Action Command dispatched" : "SDK failed to send Action Command"; + } catch (const ob::Error& error) { + response->success = false; + response->message = formatObError(error); + } catch (const std::exception& error) { + response->success = false; + response->message = error.what(); + } catch (...) { + response->success = false; + response->message = "Unknown error"; + } +} + +} // namespace orbbec_camera + +RCLCPP_COMPONENTS_REGISTER_NODE(orbbec_camera::GigEActionCommandNode) diff --git a/orbbec_camera/src/ros_service.cpp b/orbbec_camera/src/ros_service.cpp index 4fd14f55..5814728e 100644 --- a/orbbec_camera/src/ros_service.cpp +++ b/orbbec_camera/src/ros_service.cpp @@ -19,6 +19,7 @@ #include #include #include +#include #include #include "orbbec_camera/utils.h" @@ -112,6 +113,8 @@ std::string OBSyncModeToString(const OBMultiDeviceSyncMode& mode) { return "SOFTWARE_TRIGGERING"; case OBMultiDeviceSyncMode::OB_MULTI_DEVICE_SYNC_MODE_HARDWARE_TRIGGERING: return "HARDWARE_TRIGGERING"; + case OBMultiDeviceSyncMode::OB_MULTI_DEVICE_SYNC_MODE_GROUP_ACTIONS: + return "GROUP_ACTIONS"; default: return "FREE_RUN"; } @@ -334,6 +337,28 @@ void OBCameraNode::setupCameraCtrlServices() { std::shared_ptr response) { getDeviceConfigCallback(request, response); }); + if (isPropertyReadable(device_, OB_PROP_ACTION_SIGNAL_COUNT_INT) && + isPropertyReadable(device_, OB_PROP_ACTION_DEVICE_KEY_INT) && + isPropertyWritable(device_, OB_PROP_ACTION_SELECTOR_INT) && + isPropertyReadable(device_, OB_PROP_ACTION_GROUP_KEY_INT) && + isPropertyReadable(device_, OB_PROP_ACTION_GROUP_MASK_INT)) { + get_action_config_srv_ = node_->create_service( + "get_action_config", [this](const std::shared_ptr request, + std::shared_ptr response) { + getActionConfigCallback(request, response); + }); + } + if (isPropertyReadable(device_, OB_PROP_ACTION_SIGNAL_COUNT_INT) && + isPropertyWritable(device_, OB_PROP_ACTION_DEVICE_KEY_INT) && + isPropertyWritable(device_, OB_PROP_ACTION_SELECTOR_INT) && + isPropertyWritable(device_, OB_PROP_ACTION_GROUP_KEY_INT) && + isPropertyWritable(device_, OB_PROP_ACTION_GROUP_MASK_INT)) { + set_action_config_srv_ = node_->create_service( + "set_action_config", [this](const std::shared_ptr request, + std::shared_ptr response) { + setActionConfigCallback(request, response); + }); + } get_sdk_version_srv_ = node_->create_service( "get_sdk_version", [this](const std::shared_ptr request, @@ -1598,6 +1623,100 @@ void OBCameraNode::getDeviceInfoCallback(const std::shared_ptr& request, + std::shared_ptr& response) { + if (!request) { + response->success = false; + response->message = "Invalid request"; + return; + } + + std::lock_guard lock(device_lock_); + try { + const int action_signal_count = device_->getIntProperty(OB_PROP_ACTION_SIGNAL_COUNT_INT); + if (action_signal_count <= 0 || + request->selector >= static_cast(action_signal_count)) { + response->success = false; + response->message = "selector must be less than action signal count " + + std::to_string(std::max(action_signal_count, 0)); + return; + } + + device_->setIntProperty(OB_PROP_ACTION_SELECTOR_INT, static_cast(request->selector)); + response->action_signal_count = static_cast(action_signal_count); + response->device_key = + static_cast(device_->getIntProperty(OB_PROP_ACTION_DEVICE_KEY_INT)); + response->group_key = + static_cast(device_->getIntProperty(OB_PROP_ACTION_GROUP_KEY_INT)); + response->group_mask = + static_cast(device_->getIntProperty(OB_PROP_ACTION_GROUP_MASK_INT)); + response->success = true; + response->message = "OK"; + } catch (const ob::Error& e) { + response->success = false; + response->message = orbbec_camera::formatObErrorWithStatus(e); + } catch (const std::exception& e) { + response->success = false; + response->message = e.what(); + } catch (...) { + response->success = false; + response->message = "unknown error"; + } +} + +void OBCameraNode::setActionConfigCallback(const std::shared_ptr& request, + std::shared_ptr& response) { + if (!request) { + response->success = false; + response->message = "Invalid request"; + return; + } + + std::lock_guard lock(device_lock_); + try { + const int action_signal_count = device_->getIntProperty(OB_PROP_ACTION_SIGNAL_COUNT_INT); + if (action_signal_count <= 0 || + request->selector >= static_cast(action_signal_count)) { + response->success = false; + response->message = "selector must be less than action signal count " + + std::to_string(std::max(action_signal_count, 0)); + return; + } + + const auto validate_property = [this](OBPropertyID property_id, uint32_t value, + const char* property_name) { + const auto range = device_->getIntPropertyRange(property_id); + const int64_t signed_value = static_cast(value); + if (signed_value < range.min || signed_value > range.max) { + throw std::out_of_range(std::string(property_name) + " must be in [" + + std::to_string(range.min) + ", " + std::to_string(range.max) + "]"); + } + }; + validate_property(OB_PROP_ACTION_DEVICE_KEY_INT, request->device_key, "device_key"); + validate_property(OB_PROP_ACTION_SELECTOR_INT, request->selector, "selector"); + validate_property(OB_PROP_ACTION_GROUP_KEY_INT, request->group_key, "group_key"); + validate_property(OB_PROP_ACTION_GROUP_MASK_INT, request->group_mask, "group_mask"); + + device_->setIntProperty(OB_PROP_ACTION_DEVICE_KEY_INT, + static_cast(request->device_key)); + device_->setIntProperty(OB_PROP_ACTION_SELECTOR_INT, static_cast(request->selector)); + device_->setIntProperty(OB_PROP_ACTION_GROUP_KEY_INT, static_cast(request->group_key)); + device_->setIntProperty(OB_PROP_ACTION_GROUP_MASK_INT, + static_cast(request->group_mask)); + response->success = true; + response->message = "OK"; + } catch (const ob::Error& e) { + response->success = false; + response->message = orbbec_camera::formatObErrorWithStatus(e); + } catch (const std::exception& e) { + response->success = false; + response->message = e.what(); + } catch (...) { + response->success = false; + response->message = "unknown error"; + } +} + void OBCameraNode::getDeviceConfigCallback(const std::shared_ptr& request, std::shared_ptr& response) { (void)request; diff --git a/orbbec_camera/src/utils.cpp b/orbbec_camera/src/utils.cpp index 3cb41255..674991f1 100644 --- a/orbbec_camera/src/utils.cpp +++ b/orbbec_camera/src/utils.cpp @@ -679,6 +679,8 @@ OBMultiDeviceSyncMode OBSyncModeFromString(const std::string &mode) { return OBMultiDeviceSyncMode::OB_MULTI_DEVICE_SYNC_MODE_SOFTWARE_TRIGGERING; } else if (mode == "HARDWARE_TRIGGERING") { return OBMultiDeviceSyncMode::OB_MULTI_DEVICE_SYNC_MODE_HARDWARE_TRIGGERING; + } else if (mode == "GROUP_ACTIONS") { + return OBMultiDeviceSyncMode::OB_MULTI_DEVICE_SYNC_MODE_GROUP_ACTIONS; } else { return OBMultiDeviceSyncMode::OB_MULTI_DEVICE_SYNC_MODE_FREE_RUN; } diff --git a/orbbec_camera_msgs/CMakeLists.txt b/orbbec_camera_msgs/CMakeLists.txt index 5212ea6d..c004d1d6 100644 --- a/orbbec_camera_msgs/CMakeLists.txt +++ b/orbbec_camera_msgs/CMakeLists.txt @@ -28,6 +28,7 @@ rosidl_generate_interfaces( "msg/RGBD.msg" "msg/StreamProfile.msg" "srv/GetBool.srv" + "srv/GetActionConfig.srv" "srv/GetDeviceConfig.srv" "srv/GetDeviceInfo.srv" "srv/GetCameraInfo.srv" @@ -35,6 +36,7 @@ rosidl_generate_interfaces( "srv/GetAwbGain.srv" "srv/GetString.srv" "srv/SetFilter.srv" + "srv/SetActionConfig.srv" "srv/SetInt32.srv" "srv/SetAwbGain.srv" "srv/SetString.srv" @@ -43,6 +45,7 @@ rosidl_generate_interfaces( "srv/SetUserCalibParams.srv" "srv/SetBagRecording.srv" "srv/SetStreamProfile.srv" + "srv/SendActionCommand.srv" "action/RunAeAwbLockTest.action" DEPENDENCIES action_msgs diff --git a/orbbec_camera_msgs/srv/GetActionConfig.srv b/orbbec_camera_msgs/srv/GetActionConfig.srv new file mode 100644 index 00000000..d45d0116 --- /dev/null +++ b/orbbec_camera_msgs/srv/GetActionConfig.srv @@ -0,0 +1,8 @@ +uint32 selector +--- +uint32 action_signal_count +uint32 device_key +uint32 group_key +uint32 group_mask +bool success +string message diff --git a/orbbec_camera_msgs/srv/SendActionCommand.srv b/orbbec_camera_msgs/srv/SendActionCommand.srv new file mode 100644 index 00000000..c8a9628a --- /dev/null +++ b/orbbec_camera_msgs/srv/SendActionCommand.srv @@ -0,0 +1,12 @@ +uint32 device_key +uint32 group_key +uint32 group_mask +# IPv4 destination. Leave empty to use the SDK broadcast address (255.255.255.255). +string destination_ip +# GVCP/PTP timestamp: upper 32 bits are seconds and lower 32 bits are nanoseconds. +# Zero sends the command immediately. +uint64 scheduled_time +--- +# Success means that the SDK dispatched the command; Action Command has no device acknowledgment. +bool success +string message diff --git a/orbbec_camera_msgs/srv/SetActionConfig.srv b/orbbec_camera_msgs/srv/SetActionConfig.srv new file mode 100644 index 00000000..b8af7a47 --- /dev/null +++ b/orbbec_camera_msgs/srv/SetActionConfig.srv @@ -0,0 +1,7 @@ +uint32 device_key +uint32 selector +uint32 group_key +uint32 group_mask +--- +bool success +string message