// Copyright 2016 Open Source Robotics Foundation, 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. #ifndef LIFECYCLE_NODE_INTERFACE_IMPL_HPP_ #define LIFECYCLE_NODE_INTERFACE_IMPL_HPP_ #include "rclcpp_lifecycle/lifecycle_node.hpp" #include #include #include #include #include #include #include "lifecycle_msgs/msg/transition_description.hpp" #include "lifecycle_msgs/msg/transition_event.h" // for getting the c-typesupport #include "lifecycle_msgs/msg/transition_event.hpp" #include "lifecycle_msgs/srv/change_state.hpp" #include "lifecycle_msgs/srv/get_state.hpp" #include "lifecycle_msgs/srv/get_available_states.hpp" #include "lifecycle_msgs/srv/get_available_transitions.hpp" #include "rcl/error_handling.h" #include "rcl_lifecycle/rcl_lifecycle.h" #include "rcl_lifecycle/transition_map.h" #include "rclcpp/node_interfaces/node_base_interface.hpp" #include "rclcpp/node_interfaces/node_services_interface.hpp" #include "rcutils/logging_macros.h" namespace rclcpp_lifecycle { class LifecycleNode::LifecycleNodeInterfaceImpl { using ChangeStateSrv = lifecycle_msgs::srv::ChangeState; using GetStateSrv = lifecycle_msgs::srv::GetState; using GetAvailableStatesSrv = lifecycle_msgs::srv::GetAvailableStates; using GetAvailableTransitionsSrv = lifecycle_msgs::srv::GetAvailableTransitions; using TransitionEventMsg = lifecycle_msgs::msg::TransitionEvent; public: LifecycleNodeInterfaceImpl( std::shared_ptr node_base_interface, std::shared_ptr node_services_interface) : node_base_interface_(node_base_interface), node_services_interface_(node_services_interface) {} ~LifecycleNodeInterfaceImpl() { rcl_node_t * node_handle = node_base_interface_->get_rcl_node_handle(); const rcl_node_options_t * node_options = rcl_node_get_options(node_handle); auto ret = rcl_lifecycle_state_machine_fini( &state_machine_, node_handle, &node_options->allocator); if (ret != RCL_RET_OK) { fprintf(stderr, "FATAL: failed to destroy rcl_state_machine\n"); } } void init() { rcl_node_t * node_handle = node_base_interface_->get_rcl_node_handle(); const rcl_node_options_t * node_options = rcl_node_get_options(node_base_interface_->get_rcl_node_handle()); state_machine_ = rcl_lifecycle_get_zero_initialized_state_machine(); // The call to initialize the state machine takes // currently five different typesupports for all publishers/services // created within the RCL_LIFECYCLE structure. // The publisher takes a C-Typesupport since the publishing (i.e. creating // the message) is done fully in RCL. // Services are handled in C++, so that it needs a C++ typesupport structure. rcl_ret_t ret = rcl_lifecycle_state_machine_init( &state_machine_, node_handle, ROSIDL_GET_MSG_TYPE_SUPPORT(lifecycle_msgs, msg, TransitionEvent), rosidl_typesupport_cpp::get_service_type_support_handle(), rosidl_typesupport_cpp::get_service_type_support_handle(), rosidl_typesupport_cpp::get_service_type_support_handle(), rosidl_typesupport_cpp::get_service_type_support_handle(), true, &node_options->allocator); if (ret != RCL_RET_OK) { throw std::runtime_error( std::string("Couldn't initialize state machine for node ") + node_base_interface_->get_name()); } { // change_state auto cb = std::bind(&LifecycleNodeInterfaceImpl::on_change_state, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3); rclcpp::any_service_callback::AnyServiceCallback any_cb; any_cb.set(cb); srv_change_state_ = std::make_shared>( node_base_interface_->get_shared_rcl_node_handle(), &state_machine_.com_interface.srv_change_state, any_cb); node_services_interface_->add_service( std::dynamic_pointer_cast(srv_change_state_), nullptr); } { // get_state auto cb = std::bind(&LifecycleNodeInterfaceImpl::on_get_state, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3); rclcpp::any_service_callback::AnyServiceCallback any_cb; any_cb.set(std::move(cb)); srv_get_state_ = std::make_shared>( node_base_interface_->get_shared_rcl_node_handle(), &state_machine_.com_interface.srv_get_state, any_cb); node_services_interface_->add_service( std::dynamic_pointer_cast(srv_get_state_), nullptr); } { // get_available_states auto cb = std::bind(&LifecycleNodeInterfaceImpl::on_get_available_states, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3); rclcpp::any_service_callback::AnyServiceCallback any_cb; any_cb.set(std::move(cb)); srv_get_available_states_ = std::make_shared>( node_base_interface_->get_shared_rcl_node_handle(), &state_machine_.com_interface.srv_get_available_states, any_cb); node_services_interface_->add_service( std::dynamic_pointer_cast(srv_get_available_states_), nullptr); } { // get_available_transitions auto cb = std::bind(&LifecycleNodeInterfaceImpl::on_get_available_transitions, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3); rclcpp::any_service_callback::AnyServiceCallback any_cb; any_cb.set(std::move(cb)); srv_get_available_transitions_ = std::make_shared>( node_base_interface_->get_shared_rcl_node_handle(), &state_machine_.com_interface.srv_get_available_transitions, any_cb); node_services_interface_->add_service( std::dynamic_pointer_cast(srv_get_available_transitions_), nullptr); } } bool register_callback(std::uint8_t lifecycle_transition, std::function & cb) { cb_map_[lifecycle_transition] = cb; return true; } void on_change_state(const std::shared_ptr header, const std::shared_ptr req, std::shared_ptr resp) { (void)header; if (rcl_lifecycle_state_machine_is_initialized(&state_machine_) != RCL_RET_OK) { throw std::runtime_error( "Can't get state. State machine is not initialized."); } rcl_lifecycle_transition_key_t cb_return_code; auto ret = change_state(req->transition.id, cb_return_code); (void) ret; // TODO(karsten1987): Lifecycle msgs have to be extended to keep both returns // 1. return is the actual transition // 2. return is whether an error occurred or not resp->success = (cb_return_code == lifecycle_msgs::msg::Transition::TRANSITION_CALLBACK_SUCCESS); } void on_get_state(const std::shared_ptr header, const std::shared_ptr req, std::shared_ptr resp) { (void)header; (void)req; if (rcl_lifecycle_state_machine_is_initialized(&state_machine_) != RCL_RET_OK) { throw std::runtime_error( "Can't get state. State machine is not initialized."); } resp->current_state.id = static_cast(state_machine_.current_state->id); resp->current_state.label = state_machine_.current_state->label; } void on_get_available_states(const std::shared_ptr header, const std::shared_ptr req, std::shared_ptr resp) { (void)header; (void)req; if (rcl_lifecycle_state_machine_is_initialized(&state_machine_) != RCL_RET_OK) { throw std::runtime_error( "Can't get available states. State machine is not initialized."); } for (uint8_t i = 0; i < state_machine_.transition_map.states_size; ++i) { lifecycle_msgs::msg::State state; state.id = static_cast(state_machine_.transition_map.states[i].id); state.label = static_cast(state_machine_.transition_map.states[i].label); resp->available_states.push_back(state); } } void on_get_available_transitions(const std::shared_ptr header, const std::shared_ptr req, std::shared_ptr resp) { (void)header; (void)req; if (rcl_lifecycle_state_machine_is_initialized(&state_machine_) != RCL_RET_OK) { throw std::runtime_error( "Can't get available transitions. State machine is not initialized."); } for (uint8_t i = 0; i < state_machine_.transition_map.transitions_size; ++i) { rcl_lifecycle_transition_t & rcl_transition = state_machine_.transition_map.transitions[i]; lifecycle_msgs::msg::TransitionDescription trans_desc; trans_desc.transition.id = rcl_transition.id; trans_desc.transition.label = rcl_transition.label; trans_desc.start_state.id = rcl_transition.start->id; trans_desc.start_state.label = rcl_transition.start->label; trans_desc.goal_state.id = rcl_transition.goal->id; trans_desc.goal_state.label = rcl_transition.goal->label; resp->available_transitions.push_back(trans_desc); } } const State & get_current_state() { current_state_ = State(state_machine_.current_state); return current_state_; } std::vector get_available_states() { std::vector states; for (uint8_t i = 0; i < state_machine_.transition_map.states_size; ++i) { State state(&state_machine_.transition_map.states[i]); states.push_back(state); } return states; } std::vector get_available_transitions() { std::vector transitions; for (uint8_t i = 0; i < state_machine_.transition_map.transitions_size; ++i) { Transition transition( &state_machine_.transition_map.transitions[i]); transitions.push_back(transition); } return transitions; } rcl_ret_t change_state(std::uint8_t lifecycle_transition, rcl_lifecycle_transition_key_t & cb_return_code) { if (rcl_lifecycle_state_machine_is_initialized(&state_machine_) != RCL_RET_OK) { RCUTILS_LOG_ERROR("Unable to change state for state machine for %s: %s", node_base_interface_->get_name(), rcl_get_error_string_safe()) return RCL_RET_ERROR; } // keep the initial state to pass to a transition callback State initial_state(state_machine_.current_state); uint8_t transition_id = lifecycle_transition; if (rcl_lifecycle_trigger_transition(&state_machine_, transition_id, true) != RCL_RET_OK) { RCUTILS_LOG_ERROR("Unable to start transition %u from current state %s: %s", transition_id, state_machine_.current_state->label, rcl_get_error_string_safe()) return RCL_RET_ERROR; } cb_return_code = execute_callback( state_machine_.current_state->id, initial_state); if (rcl_lifecycle_trigger_transition( &state_machine_, cb_return_code, true) != RCL_RET_OK) { RCUTILS_LOG_ERROR("Failed to finish transition %u. Current state is now: %s", transition_id, state_machine_.current_state->label) return RCL_RET_ERROR; } // error handling ?! // TODO(karsten1987): iterate over possible ret value if (cb_return_code == lifecycle_msgs::msg::Transition::TRANSITION_CALLBACK_ERROR) { RCUTILS_LOG_WARN("Error occurred while doing error handling.") rcl_lifecycle_transition_key_t error_resolved = execute_callback( state_machine_.current_state->id, initial_state); if (error_resolved == lifecycle_msgs::msg::Transition::TRANSITION_CALLBACK_SUCCESS) { // We call cleanup on the error state if (rcl_lifecycle_trigger_transition(&state_machine_, error_resolved, true) != RCL_RET_OK) { RCUTILS_LOG_ERROR("Failed to call cleanup on error state") return RCL_RET_ERROR; } } else { // We call shutdown on the error state if (rcl_lifecycle_trigger_transition(&state_machine_, error_resolved, true) != RCL_RET_OK) { RCUTILS_LOG_ERROR("Failed to call cleanup on error state") return RCL_RET_ERROR; } } } // This true holds in both cases where the actual callback // was successful or not, since at this point we have a valid transistion // to either a new primary state or error state return RCL_RET_OK; } rcl_lifecycle_transition_key_t execute_callback(unsigned int cb_id, const State & previous_state) { // in case no callback was attached, we forward directly rcl_lifecycle_transition_key_t cb_success = lifecycle_msgs::msg::Transition::TRANSITION_CALLBACK_SUCCESS; auto it = cb_map_.find(cb_id); if (it != cb_map_.end()) { auto callback = it->second; try { cb_success = callback(State(previous_state)); } catch (const std::exception &) { // TODO(karsten1987): Windows CI doens't let me print the msg here // the todo is to forward the exception to the on_error callback // fprintf(stderr, "Caught exception in callback for transition %d\n", // it->first); // fprintf(stderr, "Original error msg: %s\n", e.what()); // maybe directly go for error handling here // and pass exception along with it cb_success = lifecycle_msgs::msg::Transition::TRANSITION_CALLBACK_ERROR; } } return cb_success; } const State & trigger_transition(uint8_t transition_id) { rcl_lifecycle_transition_key_t error; change_state(transition_id, error); (void) error; return get_current_state(); } const State & trigger_transition(uint8_t transition_id, rcl_lifecycle_transition_key_t & cb_return_code) { change_state(transition_id, cb_return_code); return get_current_state(); } void add_publisher_handle(std::shared_ptr pub) { weak_pubs_.push_back(pub); } void add_timer_handle(std::shared_ptr timer) { weak_timers_.push_back(timer); } rcl_lifecycle_state_machine_t state_machine_; State current_state_; std::map< std::uint8_t, std::function> cb_map_; using NodeBasePtr = std::shared_ptr; using NodeServicesPtr = std::shared_ptr; using ChangeStateSrvPtr = std::shared_ptr>; using GetStateSrvPtr = std::shared_ptr>; using GetAvailableStatesSrvPtr = std::shared_ptr>; using GetAvailableTransitionsSrvPtr = std::shared_ptr>; NodeBasePtr node_base_interface_; NodeServicesPtr node_services_interface_; ChangeStateSrvPtr srv_change_state_; GetStateSrvPtr srv_get_state_; GetAvailableStatesSrvPtr srv_get_available_states_; GetAvailableTransitionsSrvPtr srv_get_available_transitions_; // to controllable things std::vector> weak_pubs_; std::vector> weak_timers_; }; } // namespace rclcpp_lifecycle #endif // LIFECYCLE_NODE_INTERFACE_IMPL_HPP_