diff --git a/rcljava/include/org_ros2_rcljava_action_ActionServerImpl.h b/rcljava/include/org_ros2_rcljava_action_ActionServerImpl.h index 28b65441..fe3097bb 100644 --- a/rcljava/include/org_ros2_rcljava_action_ActionServerImpl.h +++ b/rcljava/include/org_ros2_rcljava_action_ActionServerImpl.h @@ -126,12 +126,47 @@ JNICALL Java_org_ros2_rcljava_action_ActionServerImpl_nativeProcessCancelRequest * Method: nativeCheckGoalExists * Signature: (JLorg/ros2/rcljava/interfaces/MessageDefinition;JJ)Z */ - JNIEXPORT jboolean JNICALL Java_org_ros2_rcljava_action_ActionServerImpl_nativeCheckGoalExists( JNIEnv * env, jclass, jlong, jobject, jlong, jlong); +/* + * Class: org_ros2_rcljava_action_ActionServerImpl + * Method: nativePublishStatus + * Signature: (J) + */ +JNIEXPORT void +JNICALL Java_org_ros2_rcljava_action_ActionServerImpl_nativePublishStatus( + JNIEnv * env, jclass, jlong); + +/* + * Class: org_ros2_rcljava_action_ActionServerImpl + * Method: nativePublishFeedbackMessage + * Signature: (JLorg/ros2/rcljava/interfaces/FeedbackMessageDefinition;JJ) + */ +JNIEXPORT void +JNICALL Java_org_ros2_rcljava_action_ActionServerImpl_nativePublishFeedbackMessage( + JNIEnv * env, jclass, jlong, jobject, jlong, jlong); + +/* + * Class: org_ros2_rcljava_action_ActionServerImpl + * Method: nativeNotifyGoalDone + * Signature: (J) + */ +JNIEXPORT void +JNICALL Java_org_ros2_rcljava_action_ActionServerImpl_nativeNotifyGoalDone( + JNIEnv * env, jclass, jlong); + +/* + * Class: org_ros2_rcljava_action_ActionServerImpl + * Method: nativeExpireGoals + * Signature: (JLaction_msgs/msg/GoalInfo;JLorg/ros2/rcljava/consumers/Consumer;) + */ +JNIEXPORT void +JNICALL Java_org_ros2_rcljava_action_ActionServerImpl_nativeExpireGoals( + JNIEnv * env, jclass, jlong, jobject, jlong, jobject); + #ifdef __cplusplus } #endif diff --git a/rcljava/include/org_ros2_rcljava_action_ActionServerImpl_GoalHandleImpl.h b/rcljava/include/org_ros2_rcljava_action_ActionServerImpl_GoalHandleImpl.h index 94aaab23..6f6f98ce 100644 --- a/rcljava/include/org_ros2_rcljava_action_ActionServerImpl_GoalHandleImpl.h +++ b/rcljava/include/org_ros2_rcljava_action_ActionServerImpl_GoalHandleImpl.h @@ -41,48 +41,12 @@ JNICALL Java_org_ros2_rcljava_action_ActionServerImpl_00024GoalHandleImpl_native /* * Class: org_ros2_rcljava_action_ActionServerImpl$GoalHandleImpl - * Method: nativeGoalEventExecute - * Signature: (J) - */ -JNIEXPORT void -JNICALL Java_org_ros2_rcljava_action_ActionServerImpl_00024GoalHandleImpl_nativeGoalEventExecute( - JNIEnv * env, jclass, jlong); - -/* - * Class: org_ros2_rcljava_action_ActionServerImpl$GoalHandleImpl - * Method: nativeGoalEventCancelGoal - * Signature: (J) - */ -JNIEXPORT void -JNICALL Java_org_ros2_rcljava_action_ActionServerImpl_00024GoalHandleImpl_nativeGoalEventCancelGoal( - JNIEnv * env, jclass, jlong); - -/* - * Class: org_ros2_rcljava_action_ActionServerImpl$GoalHandleImpl - * Method: nativeGoalEventSucceed - * Signature: (J) - */ -JNIEXPORT void -JNICALL Java_org_ros2_rcljava_action_ActionServerImpl_00024GoalHandleImpl_nativeGoalEventSucceed( - JNIEnv * env, jclass, jlong); - -/* - * Class: org_ros2_rcljava_action_ActionServerImpl$GoalHandleImpl - * Method: nativeGoalEventAbort - * Signature: (J) - */ -JNIEXPORT void -JNICALL Java_org_ros2_rcljava_action_ActionServerImpl_00024GoalHandleImpl_nativeGoalEventAbort( - JNIEnv * env, jclass, jlong); - -/* - * Class: org_ros2_rcljava_action_ActionServerImpl$GoalHandleImpl - * Method: nativeGoalEventCanceled - * Signature: (J) + * Method: nativeUpdateGoalState + * Signature: (JJ) */ JNIEXPORT void -JNICALL Java_org_ros2_rcljava_action_ActionServerImpl_00024GoalHandleImpl_nativeGoalEventCanceled( - JNIEnv * env, jclass, jlong); +JNICALL Java_org_ros2_rcljava_action_ActionServerImpl_00024GoalHandleImpl_nativeUpdateGoalState( + JNIEnv * env, jclass, jlong jgoal_handle, jlong jevent); /* * Class: org_ros2_rcljava_action_ActionServerImpl$GoalHandleImpl diff --git a/rcljava/src/main/cpp/org_ros2_rcljava_action_ActionServerImpl.cpp b/rcljava/src/main/cpp/org_ros2_rcljava_action_ActionServerImpl.cpp index 2d48fef1..47a89440 100644 --- a/rcljava/src/main/cpp/org_ros2_rcljava_action_ActionServerImpl.cpp +++ b/rcljava/src/main/cpp/org_ros2_rcljava_action_ActionServerImpl.cpp @@ -21,6 +21,7 @@ #include "rcl/error_handling.h" #include "rcl/rcl.h" #include "rcl_action/rcl_action.h" +#include "rcpputils/scope_exit.hpp" #include "rosidl_runtime_c/message_type_support_struct.h" #include "rcljava_common/exceptions.hpp" @@ -30,6 +31,7 @@ #include "./convert.hpp" +using rcljava_common::exceptions::rcljava_throw_exception; using rcljava_common::exceptions::rcljava_throw_rclexception; using rcljava_common::signatures::convert_from_java_signature; using rcljava_common::signatures::convert_to_java_signature; @@ -363,3 +365,105 @@ JNICALL Java_org_ros2_rcljava_action_ActionServerImpl_nativeCheckGoalExists( return exists; } + +JNIEXPORT void +JNICALL Java_org_ros2_rcljava_action_ActionServerImpl_nativePublishStatus( + JNIEnv * env, jclass, jlong jaction_server) +{ + assert(0 != jaction_server); + auto * action_server = reinterpret_cast(jaction_server); + rcl_action_goal_status_array_t status_message = + rcl_action_get_zero_initialized_goal_status_array(); + rcl_ret_t ret = rcl_action_get_goal_status_array(action_server, &status_message); + if (ret != RCL_RET_OK) { + std::string msg = \ + "Failed to get goal status array: " + std::string(rcl_get_error_string().str); + rcl_reset_error(); + rcljava_throw_rclexception(env, ret, msg); + return; + } + + ret = rcl_action_publish_status(action_server, &status_message); + + if (ret != RCL_RET_OK) { + std::string msg = \ + "Failed to publish status array: " + std::string(rcl_get_error_string().str); + rcl_reset_error(); + rcljava_throw_rclexception(env, ret, msg); + return; + } +} + +JNIEXPORT void +JNICALL Java_org_ros2_rcljava_action_ActionServerImpl_nativePublishFeedbackMessage( + JNIEnv * env, jclass, jlong jaction_server, jobject jfeedback_msg, + jlong jfeedback_from_java, jlong jfeedback_destroy) +{ + assert(0 != jaction_server); + assert(nullptr != jfeedback_msg); + assert(0 != jfeedback_from_java); + assert(0 != jfeedback_destroy); + + auto * action_server = reinterpret_cast(jaction_server); + auto destroy_feedback = reinterpret_cast(jfeedback_destroy); + auto from_java_feedback = reinterpret_cast(jfeedback_from_java); + void * feedback = from_java_feedback(jfeedback_msg, nullptr); + auto destroy_feedback_scope_exit = rcpputils::make_scope_exit( + [feedback, destroy_feedback]() {destroy_feedback(feedback);}); + RCLJAVA_COMMON_CHECK_FOR_EXCEPTION(env); + if (!feedback) { + return; + } + rcl_ret_t ret = rcl_action_publish_feedback(action_server, feedback); + RCLJAVA_COMMON_THROW_FROM_RCL(env, ret, "Failed to publish feedback"); +} + +JNIEXPORT void +JNICALL Java_org_ros2_rcljava_action_ActionServerImpl_nativeNotifyGoalDone( + JNIEnv * env, jclass, jlong jaction_server) +{ + assert(0 != jaction_server); + auto * action_server = reinterpret_cast(jaction_server); + rcl_ret_t ret = rcl_action_notify_goal_done(action_server); + RCLJAVA_COMMON_THROW_FROM_RCL(env, ret, "Failed to notify goal done"); +} + +JNIEXPORT void +JNICALL Java_org_ros2_rcljava_action_ActionServerImpl_nativeExpireGoals( + JNIEnv * env, jclass, jlong jaction_server, jobject jgoal_info, + jlong jgoal_info_to_java, jobject jaccept) +{ + assert(0 != jaction_server); + auto * action_server = reinterpret_cast(jaction_server); + + rcl_action_goal_info_t expired_goal; + size_t num_expired = 1; + + // Loop in case more than 1 goal expired + jclass jaccept_class = env->GetObjectClass(jaccept); + if (!jaccept_class) { + rcljava_throw_exception( + env, "java/lang/IllegalStateException", + "nativeExpireGoals(): could not find jaccept class"); + return; + } + jmethodID jaccept_mid = env->GetMethodID( + jaccept_class, "accept", "(Laction_msgs/msg/GoalInfo;)V"); + if (!jaccept_mid) { + rcljava_throw_exception( + env, "java/lang/IllegalStateException", + "nativeExpireGoals(): could not find jaccept accept method"); + return; + } + while (num_expired > 0u) { + rcl_ret_t ret; + ret = rcl_action_expire_goals(action_server, &expired_goal, 1, &num_expired); + RCLJAVA_COMMON_THROW_FROM_RCL(env, ret, "Failed to expire goals"); + if (num_expired) { + auto goal_info_to_java = reinterpret_cast(jgoal_info_to_java); + goal_info_to_java(&expired_goal, jgoal_info); + env->CallVoidMethod(jaccept, jaccept_mid, jgoal_info); + RCLJAVA_COMMON_CHECK_FOR_EXCEPTION(env); + } + } +} diff --git a/rcljava/src/main/cpp/org_ros2_rcljava_action_ActionServerImpl_GoalHandleImpl.cpp b/rcljava/src/main/cpp/org_ros2_rcljava_action_ActionServerImpl_GoalHandleImpl.cpp index de1fb4b6..85185eaa 100644 --- a/rcljava/src/main/cpp/org_ros2_rcljava_action_ActionServerImpl_GoalHandleImpl.cpp +++ b/rcljava/src/main/cpp/org_ros2_rcljava_action_ActionServerImpl_GoalHandleImpl.cpp @@ -26,6 +26,7 @@ #include "org_ros2_rcljava_action_ActionServerImpl_GoalHandleImpl.h" +using rcljava_common::exceptions::rcljava_throw_exception; using rcljava_common::exceptions::rcljava_throw_rclexception; using rcljava_common::signatures::convert_from_java_signature; using rcljava_common::signatures::destroy_ros_message_signature; @@ -81,10 +82,40 @@ JNICALL Java_org_ros2_rcljava_action_ActionServerImpl_00024GoalHandleImpl_native return static_cast(status); } -static void update_goal_state(JNIEnv * env, jlong jgoal_handle, rcl_action_goal_event_t event) +JNIEXPORT void +JNICALL Java_org_ros2_rcljava_action_ActionServerImpl_00024GoalHandleImpl_nativeUpdateGoalState( + JNIEnv * env, jclass, jlong jgoal_handle, jlong jto_status) { - rcl_action_goal_handle_t * goal_handle = reinterpret_cast( - jgoal_handle); + assert(0 != jgoal_handle); + rcl_action_goal_event_t event = GOAL_EVENT_NUM_EVENTS; + switch (jto_status) { + case GOAL_STATE_EXECUTING: + event = GOAL_EVENT_EXECUTE; + break; + case GOAL_STATE_CANCELING: + event = GOAL_EVENT_CANCEL_GOAL; + break; + case GOAL_STATE_SUCCEEDED: + event = GOAL_EVENT_SUCCEED; + break; + case GOAL_STATE_CANCELED: + event = GOAL_EVENT_CANCELED; + break; + case GOAL_STATE_ABORTED: + event = GOAL_EVENT_ABORT; + break; + case GOAL_STATE_ACCEPTED: // fallthrough + case GOAL_STATE_UNKNOWN: // fallthrough + default: + break; + } + if (event >= GOAL_EVENT_NUM_EVENTS) { + rcljava_throw_exception( + env, "java/lang/IllegalStateException", "nativeUpdateGoalState(): Unknown event"); + return; + } + + auto * goal_handle = reinterpret_cast(jgoal_handle); rcl_ret_t ret = rcl_action_update_goal_state(goal_handle, event); if (RCL_RET_OK != ret) { std::string msg = "Failed to update goal state with event: " + @@ -94,41 +125,6 @@ static void update_goal_state(JNIEnv * env, jlong jgoal_handle, rcl_action_goal_ } } -JNIEXPORT void -JNICALL Java_org_ros2_rcljava_action_ActionServerImpl_00024GoalHandleImpl_nativeGoalEventExecute( - JNIEnv * env, jclass, jlong jgoal_handle) -{ - update_goal_state(env, jgoal_handle, GOAL_EVENT_EXECUTE); -} - -JNIEXPORT void -JNICALL Java_org_ros2_rcljava_action_ActionServerImpl_00024GoalHandleImpl_nativeGoalEventCancelGoal( - JNIEnv * env, jclass, jlong jgoal_handle) -{ - update_goal_state(env, jgoal_handle, GOAL_EVENT_CANCEL_GOAL); -} - -JNIEXPORT void -JNICALL Java_org_ros2_rcljava_action_ActionServerImpl_00024GoalHandleImpl_nativeGoalEventSucceed( - JNIEnv * env, jclass, jlong jgoal_handle) -{ - update_goal_state(env, jgoal_handle, GOAL_EVENT_SUCCEED); -} - -JNIEXPORT void -JNICALL Java_org_ros2_rcljava_action_ActionServerImpl_00024GoalHandleImpl_nativeGoalEventAbort( - JNIEnv * env, jclass, jlong jgoal_handle) -{ - update_goal_state(env, jgoal_handle, GOAL_EVENT_ABORT); -} - -JNIEXPORT void -JNICALL Java_org_ros2_rcljava_action_ActionServerImpl_00024GoalHandleImpl_nativeGoalEventCanceled( - JNIEnv * env, jclass, jlong jgoal_handle) -{ - update_goal_state(env, jgoal_handle, GOAL_EVENT_CANCELED); -} - JNIEXPORT void JNICALL Java_org_ros2_rcljava_action_ActionServerImpl_00024GoalHandleImpl_nativeDipose( JNIEnv * env, jclass, jlong jgoal_handle) diff --git a/rcljava/src/main/java/org/ros2/rcljava/action/ActionServerGoalHandle.java b/rcljava/src/main/java/org/ros2/rcljava/action/ActionServerGoalHandle.java index 11117437..6f207319 100644 --- a/rcljava/src/main/java/org/ros2/rcljava/action/ActionServerGoalHandle.java +++ b/rcljava/src/main/java/org/ros2/rcljava/action/ActionServerGoalHandle.java @@ -17,9 +17,21 @@ import org.ros2.rcljava.interfaces.ActionDefinition; import org.ros2.rcljava.interfaces.Disposable; -import org.ros2.rcljava.interfaces.MessageDefinition; +import org.ros2.rcljava.interfaces.GoalDefinition; +import org.ros2.rcljava.interfaces.FeedbackDefinition; +import org.ros2.rcljava.interfaces.ResultDefinition; public interface ActionServerGoalHandle extends Disposable { + /** + * Get the action result type. + */ + public Class getResultType(); + + /** + * Get the action feedback type. + */ + public Class getFeedbackType(); + /** * Get the message containing the timestamp and ID for the goal. */ @@ -28,7 +40,7 @@ public interface ActionServerGoalHandle extends Disp /** * Get the goal message. */ - public MessageDefinition getGoal(); + public GoalDefinition getGoal(); /** * Get the goal status. @@ -52,19 +64,26 @@ public interface ActionServerGoalHandle extends Disp * * Pre-condition: the goal must be in the EXECUTING or CANCELING state. */ - public void succeed(); + public void succeed(ResultDefinition result); /** * Transition the goal the the CANCELED state. * * Pre-condition: the goal must be in the CANCELING state. */ - public void canceled(); + public void canceled(ResultDefinition result); /** * Transition the goal the the CANCELED state. * * Pre-condition: the goal must be in the EXCUTING or CANCELING state. */ - public void abort(); + public void abort(ResultDefinition result); + + /** + * Send an update about the progress of a goal. + * + * Pre-condition: the goal must be in the EXCUTING state. + */ + public void publishFeedback(FeedbackDefinition result); } diff --git a/rcljava/src/main/java/org/ros2/rcljava/action/ActionServerImpl.java b/rcljava/src/main/java/org/ros2/rcljava/action/ActionServerImpl.java index b7e740c8..0a2aee98 100644 --- a/rcljava/src/main/java/org/ros2/rcljava/action/ActionServerImpl.java +++ b/rcljava/src/main/java/org/ros2/rcljava/action/ActionServerImpl.java @@ -27,8 +27,14 @@ import org.ros2.rcljava.consumers.Consumer; import org.ros2.rcljava.interfaces.MessageDefinition; import org.ros2.rcljava.interfaces.ActionDefinition; +import org.ros2.rcljava.interfaces.GoalDefinition; import org.ros2.rcljava.interfaces.GoalRequestDefinition; import org.ros2.rcljava.interfaces.GoalResponseDefinition; +import org.ros2.rcljava.interfaces.FeedbackDefinition; +import org.ros2.rcljava.interfaces.FeedbackMessageDefinition; +import org.ros2.rcljava.interfaces.ResultDefinition; +import org.ros2.rcljava.interfaces.ResultRequestDefinition; +import org.ros2.rcljava.interfaces.ResultResponseDefinition; import org.ros2.rcljava.node.Node; import org.ros2.rcljava.service.RMWRequestId; import org.ros2.rcljava.time.Clock; @@ -53,9 +59,8 @@ public class ActionServerImpl implements ActionServe class GoalHandleImpl implements ActionServerGoalHandle { private long handle; - private ActionServer actionServer; private action_msgs.msg.GoalInfo goalInfo; - private MessageDefinition goal; + private GoalDefinition goal; private native long nativeAcceptNewGoal( long actionServerHandle, @@ -63,28 +68,37 @@ private native long nativeAcceptNewGoal( long goalInfoDestructorHandle, MessageDefinition goalInfo); private native int nativeGetStatus(long goalHandle); - private native void nativeGoalEventExecute(long goalHandle); - private native void nativeGoalEventCancelGoal(long goalHandle); - private native void nativeGoalEventSucceed(long goalHandle); - private native void nativeGoalEventAbort(long goalHandle); - private native void nativeGoalEventCanceled(long goalHandle); + private native void nativeUpdateGoalState(long goalHandle, long event); private native void nativeDispose(long handle); public GoalHandleImpl( - ActionServer actionServer, action_msgs.msg.GoalInfo goalInfo, MessageDefinition goal) + action_msgs.msg.GoalInfo goalInfo, GoalDefinition goal) { - this.actionServer = actionServer; this.goalInfo = goalInfo; this.goal = goal; long goalInfoFromJavaConverterHandle = goalInfo.getFromJavaConverterInstance(); long goalInfoDestructorHandle = goalInfo.getDestructorInstance(); this.handle = nativeAcceptNewGoal( - actionServer.getHandle(), + ActionServerImpl.this.getHandle(), goalInfoFromJavaConverterHandle, goalInfoDestructorHandle, goalInfo); } + /** + * {@inheritDoc} + */ + public Class getResultType() { + return ActionServerImpl.this.actionTypeInstance.getResultType(); + } + + /** + * {@inheritDoc} + */ + public Class getFeedbackType() { + return ActionServerImpl.this.actionTypeInstance.getFeedbackType(); + } + /** * {@inheritDoc} */ @@ -95,7 +109,7 @@ public action_msgs.msg.GoalInfo getGoalInfo() { /** * {@inheritDoc} */ - public MessageDefinition getGoal() { + public GoalDefinition getGoal() { return this.goal; } @@ -122,7 +136,7 @@ public synchronized void execute() { // In this case we want to avoid the illegal state transition to EXECUTING // but still call the users execute callback to let them handle canceling the goal. if (!this.isCanceling()) { - nativeGoalEventExecute(this.handle); + nativeUpdateGoalState(this.handle, action_msgs.msg.GoalStatus.STATUS_EXECUTING); } } @@ -130,28 +144,45 @@ public synchronized void execute() { * Transition the goal to the CANCELING state. */ public synchronized void cancelGoal() { - nativeGoalEventCancelGoal(this.handle); + nativeUpdateGoalState(this.handle, action_msgs.msg.GoalStatus.STATUS_CANCELING); } /** * {@inheritDoc} */ - public synchronized void succeed() { - nativeGoalEventSucceed(this.handle); + public synchronized void succeed(ResultDefinition result) { + this.toTerminalState(action_msgs.msg.GoalStatus.STATUS_SUCCEEDED, result); } /** * {@inheritDoc} */ - public synchronized void canceled() { - nativeGoalEventCanceled(this.handle); + public synchronized void canceled(ResultDefinition result) { + this.toTerminalState(action_msgs.msg.GoalStatus.STATUS_CANCELED, result); } /** * {@inheritDoc} */ - public synchronized void abort() { - nativeGoalEventAbort(this.handle); + public synchronized void abort(ResultDefinition result) { + this.toTerminalState(action_msgs.msg.GoalStatus.STATUS_ABORTED, result); + } + + /** + * {@inheritDoc} + */ + public synchronized void publishFeedback(FeedbackDefinition feedback) { + Class feedbackMessageType = + ActionServerImpl.this.actionTypeInstance.getFeedbackMessageType(); + FeedbackMessageDefinition feedbackMessage = null; + try { + feedbackMessage = feedbackMessageType.newInstance(); + } catch (Exception ex) { + throw new IllegalArgumentException("Failed to instantiate feedback message", ex); + } + feedbackMessage.setFeedback(feedback); + feedbackMessage.setGoalUuid(this.goalInfo.getGoalId().getUuidAsList()); + ActionServerImpl.this.publishFeedbackMessage(feedbackMessage); } /** @@ -168,6 +199,17 @@ public synchronized final void dispose() { public final long getHandle() { return this.handle; } + + private synchronized final void toTerminalState(byte status, ResultDefinition result) { + nativeUpdateGoalState(this.handle, status); + ResultResponseDefinition resultResponse = ActionServerImpl.this.createResultResponse(); + resultResponse.setGoalStatus(status); + resultResponse.setResult(result); + ActionServerImpl.this.sendResult(goalInfo.getGoalId().getUuidAsList(), resultResponse); + ActionServerImpl.this.publishStatus(); + ActionServerImpl.this.notifyGoalDone(); + ActionServerImpl.this.goalHandles.remove(this.goalInfo.getGoalId().getUuidAsList()); + } } // class GoalHandleImpl private final WeakReference nodeReference; @@ -182,6 +224,8 @@ public final long getHandle() { private boolean[] readyEntities; private Map, GoalHandleImpl> goalHandles; + private Map, List> goalRequests; + private Map, ResultResponseDefinition> goalResults; private boolean isGoalRequestReady() { return this.readyEntities[0]; @@ -231,6 +275,8 @@ public ActionServerImpl( this.acceptedCallback = acceptedCallback; this.goalHandles = new HashMap, GoalHandleImpl>(); + this.goalRequests = new HashMap, List>(); + this.goalResults = new HashMap, ResultResponseDefinition>(); Node node = nodeReference.get(); if (node == null) { @@ -301,18 +347,10 @@ private ActionServerGoalHandle executeGoalRequest( // Create and populate a GoalInfo message List goalUuid = requestMessage.getGoalUuid(); - action_msgs.msg.GoalInfo goalInfo = new action_msgs.msg.GoalInfo(); - unique_identifier_msgs.msg.UUID uuidMessage= new unique_identifier_msgs.msg.UUID(); - uuidMessage.setUuid(goalUuid); - goalInfo.setGoalId(uuidMessage); - goalInfo.setStamp(timeRequestHandled); - - long goalInfoFromJavaConverterHandle = goalInfo.getFromJavaConverterInstance(); - long goalInfoDestructorHandle = goalInfo.getDestructorInstance(); + action_msgs.msg.GoalInfo goalInfo = this.createGoalInfo(goalUuid); // Check that the goal ID isn't already being used - boolean goalExists = nativeCheckGoalExists( - this.handle, goalInfo, goalInfoFromJavaConverterHandle, goalInfoDestructorHandle); + boolean goalExists = this.goalExists(goalInfo); if (goalExists) { logger.warn("Received goal request for goal already being tracked by action server. Goal ID: " + goalUuid); responseMessage.accept(false); @@ -326,14 +364,13 @@ private ActionServerGoalHandle executeGoalRequest( || GoalCallback.GoalResponse.ACCEPT_AND_EXECUTE == response; responseMessage.accept(accepted); - System.out.println("Goal request handled " + accepted); if (!accepted) { return null; } // Create a goal handle and add it to the list of goals GoalHandleImpl goalHandle = this.new GoalHandleImpl( - this, goalInfo, requestMessage.getGoal()); + goalInfo, requestMessage.getGoal()); this.goalHandles.put(requestMessage.getGoalUuid(), goalHandle); if (GoalCallback.GoalResponse.ACCEPT_AND_EXECUTE == response) { goalHandle.execute(); @@ -373,6 +410,23 @@ private action_msgs.srv.CancelGoal_Response executeCancelRequest( return outputMessage; } + private void sendResultResponse( + RMWRequestId rmwRequestId, + ResultResponseDefinition resultResponse) + { + long resultFromJavaConverterHandle = resultResponse.getFromJavaConverterInstance(); + long resultToJavaConverterHandle = resultResponse.getToJavaConverterInstance(); + long resultDestructorHandle = resultResponse.getDestructorInstance(); + + nativeSendResultResponse( + this.handle, + rmwRequestId, + resultFromJavaConverterHandle, + resultToJavaConverterHandle, + resultDestructorHandle, + resultResponse); + } + private static native RMWRequestId nativeTakeGoalRequest( long actionServerHandle, long requestFromJavaConverterHandle, @@ -432,6 +486,95 @@ private static native boolean nativeCheckGoalExists( long goalInfoFromJavaConverterHandle, long goalInfoDestructorHandle); + private boolean goalExists(action_msgs.msg.GoalInfo goalInfo) { + long goalInfoFromJavaConverterHandle = goalInfo.getFromJavaConverterInstance(); + long goalInfoDestructorHandle = goalInfo.getDestructorInstance(); + + return nativeCheckGoalExists( + this.handle, goalInfo, goalInfoFromJavaConverterHandle, goalInfoDestructorHandle); + } + + private static native void nativePublishStatus(long handle); + + private void publishStatus() { + this.nativePublishStatus(this.handle); + } + + private static native void nativePublishFeedbackMessage( + long handle, + FeedbackMessageDefinition feedbackMessage, + long feedbackMessageFromJavaConverterHandle, + long feedbackMessageDestructorHandle); + + private void publishFeedbackMessage(FeedbackMessageDefinition feedbackMessage) { + this.nativePublishFeedbackMessage( + this.handle, + feedbackMessage, + feedbackMessage.getFromJavaConverterInstance(), + feedbackMessage.getDestructorInstance()); + } + + private static native void nativeNotifyGoalDone(long handle); + + private void notifyGoalDone() { + this.nativeNotifyGoalDone(this.handle); + } + + private static native void nativeExpireGoals( + long handle, action_msgs.msg.GoalInfo goalInfo, + long goalInfoToJavaConverterHandle, Consumer onExpiredGoal); + + private void expireGoals() { + action_msgs.msg.GoalInfo goalInfo = new action_msgs.msg.GoalInfo(); + long goalInfoToJavaConverterHandle = goalInfo.getFromJavaConverterInstance(); + nativeExpireGoals( + this.handle, goalInfo, goalInfoToJavaConverterHandle, + new Consumer() { + public void accept(action_msgs.msg.GoalInfo goalInfo) { + List goalUuid = goalInfo.getGoalId().getUuidAsList(); + ActionServerImpl.this.goalResults.remove(goalUuid); + ActionServerImpl.this.goalRequests.remove(goalUuid); + ActionServerImpl.this.goalHandles.remove(goalUuid); + } + }); + } + + private action_msgs.msg.GoalInfo createGoalInfo(List goalUuid) { + action_msgs.msg.GoalInfo goalInfo = new action_msgs.msg.GoalInfo(); + unique_identifier_msgs.msg.UUID uuidMessage= new unique_identifier_msgs.msg.UUID(); + uuidMessage.setUuid(goalUuid); + goalInfo.setGoalId(uuidMessage); + return goalInfo; + } + + private ResultResponseDefinition createResultResponse() { + ResultResponseDefinition resultResponse; + try { + resultResponse = this.actionTypeInstance.getGetResultResponseType().newInstance(); + } catch (Exception ex) { + throw new IllegalArgumentException("Failed to instantiate provided action type", ex); + } + return resultResponse; + } + + // This will store the result, so it can be sent to future result requests, and + // will also send a result response to all requests that were already made. + private void sendResult(List goalUuid, ResultResponseDefinition resultResponse) { + boolean goalExists = this.goalExists(this.createGoalInfo(goalUuid)); + if (!goalExists) { + throw new IllegalStateException("Asked to publish result for goal that does not exist"); + } + this.goalResults.put(goalUuid, resultResponse); + + // if there are clients who already asked for the result, send it to them + List requests = this.goalRequests.get(goalUuid); + if (requests != null) { + for (RMWRequestId request : requests) { + this.sendResultResponse(request, resultResponse); + } + } + } + /** * {@inheritDoc} */ @@ -509,13 +652,57 @@ public void execute() { } if (this.isResultRequestReady()) { - // executeResultRequest(rmwRequestId, requestMessage, responseMessage); - // TODO + Class requestType = this.actionTypeInstance.getGetResultRequestType(); + + ResultRequestDefinition requestMessage = null; + try { + requestMessage = requestType.newInstance(); + } catch (Exception ex) { + throw new IllegalArgumentException("Failed to instantiate action result request", ex); + } + + if (requestMessage != null) { + long requestFromJavaConverterHandle = requestMessage.getFromJavaConverterInstance(); + long requestToJavaConverterHandle = requestMessage.getToJavaConverterInstance(); + long requestDestructorHandle = requestMessage.getDestructorInstance(); + + RMWRequestId rmwRequestId = + nativeTakeResultRequest( + this.handle, + requestFromJavaConverterHandle, requestToJavaConverterHandle, requestDestructorHandle, + requestMessage); + + if (rmwRequestId == null) { + return; + } + + List goalUuid = requestMessage.getGoalUuid(); + boolean goalExists = this.goalExists(this.createGoalInfo(goalUuid)); + + ResultResponseDefinition resultResponse = null; + if (!goalExists) { + resultResponse = this.createResultResponse(); + resultResponse.setGoalStatus(action_msgs.msg.GoalStatus.STATUS_UNKNOWN); + } else { + resultResponse = this.goalResults.get(goalUuid); + } + + if (null == resultResponse) { + List requestIds = null; + requestIds = this.goalRequests.get(goalUuid); + if (requestIds == null) { + requestIds = new ArrayList(); + this.goalRequests.put(goalUuid, requestIds); + } + requestIds.add(rmwRequestId); + } else { + this.sendResultResponse(rmwRequestId, resultResponse); + } + } } if (this.isGoalExpiredReady()) { - // cleanupExpiredGoals(); - // TODO + this.expireGoals(); } } diff --git a/rcljava/src/main/java/org/ros2/rcljava/action/GoalStatus.java b/rcljava/src/main/java/org/ros2/rcljava/action/GoalStatus.java index aee9e1c9..abacc6df 100644 --- a/rcljava/src/main/java/org/ros2/rcljava/action/GoalStatus.java +++ b/rcljava/src/main/java/org/ros2/rcljava/action/GoalStatus.java @@ -16,32 +16,31 @@ package org.ros2.rcljava.action; public enum GoalStatus { - UNKNOWN, - ACCEPTED, - EXECUTING, - CANCELING, - SUCCEEDED, - CANCELED, - ABORTED; + UNKNOWN(action_msgs.msg.GoalStatus.STATUS_UNKNOWN), + ACCEPTED(action_msgs.msg.GoalStatus.STATUS_ACCEPTED), + EXECUTING(action_msgs.msg.GoalStatus.STATUS_EXECUTING), + CANCELING(action_msgs.msg.GoalStatus.STATUS_CANCELING), + SUCCEEDED(action_msgs.msg.GoalStatus.STATUS_SUCCEEDED), + CANCELED(action_msgs.msg.GoalStatus.STATUS_CANCELED), + ABORTED(action_msgs.msg.GoalStatus.STATUS_ABORTED); - public static GoalStatus fromMessageValue(byte status) { - switch (status) { - case action_msgs.msg.GoalStatus.STATUS_ACCEPTED: - return GoalStatus.ACCEPTED; - case action_msgs.msg.GoalStatus.STATUS_EXECUTING: - return GoalStatus.EXECUTING; - case action_msgs.msg.GoalStatus.STATUS_CANCELING: - return GoalStatus.CANCELING; - case action_msgs.msg.GoalStatus.STATUS_SUCCEEDED: - return GoalStatus.SUCCEEDED; - case action_msgs.msg.GoalStatus.STATUS_CANCELED: - return GoalStatus.CANCELED; - case action_msgs.msg.GoalStatus.STATUS_ABORTED: - return GoalStatus.ABORTED; - case action_msgs.msg.GoalStatus.STATUS_UNKNOWN: - default: - return GoalStatus.UNKNOWN; + public static GoalStatus fromMessageValue(byte value) { + for (GoalStatus status: GoalStatus.values()) { + if (status.status == value) { + return status; + } } + return GoalStatus.ABORTED; } + + public byte toMessageValue() { + return this.status; + } + + private GoalStatus(byte status) { + this.status = status; + } + + private byte status; } diff --git a/rcljava_common/CMakeLists.txt b/rcljava_common/CMakeLists.txt index 46715393..f6c3f2eb 100644 --- a/rcljava_common/CMakeLists.txt +++ b/rcljava_common/CMakeLists.txt @@ -38,9 +38,15 @@ set(${PROJECT_NAME}_java_sources "src/main/java/org/ros2/rcljava/exceptions/RCLReturn.java" "src/main/java/org/ros2/rcljava/interfaces/ActionDefinition.java" "src/main/java/org/ros2/rcljava/interfaces/Disposable.java" - "src/main/java/org/ros2/rcljava/interfaces/GoalRequestDefinition.java" + "src/main/java/org/ros2/rcljava/interfaces/FeedbackDefinition.java" + "src/main/java/org/ros2/rcljava/interfaces/FeedbackMessageDefinition.java" + "src/main/java/org/ros2/rcljava/interfaces/GoalDefinition.java" "src/main/java/org/ros2/rcljava/interfaces/GoalResponseDefinition.java" + "src/main/java/org/ros2/rcljava/interfaces/GoalRequestDefinition.java" "src/main/java/org/ros2/rcljava/interfaces/MessageDefinition.java" + "src/main/java/org/ros2/rcljava/interfaces/ResultDefinition.java" + "src/main/java/org/ros2/rcljava/interfaces/ResultRequestDefinition.java" + "src/main/java/org/ros2/rcljava/interfaces/ResultResponseDefinition.java" "src/main/java/org/ros2/rcljava/interfaces/ServiceDefinition.java" ) diff --git a/rcljava_common/src/main/java/org/ros2/rcljava/interfaces/ActionDefinition.java b/rcljava_common/src/main/java/org/ros2/rcljava/interfaces/ActionDefinition.java index e39d65d8..d225901e 100644 --- a/rcljava_common/src/main/java/org/ros2/rcljava/interfaces/ActionDefinition.java +++ b/rcljava_common/src/main/java/org/ros2/rcljava/interfaces/ActionDefinition.java @@ -18,6 +18,9 @@ public interface ActionDefinition { Class getSendGoalRequestType(); Class getSendGoalResponseType(); - Class getGetResultRequestType(); - Class getGetResultResponseType(); + Class getGetResultRequestType(); + Class getGetResultResponseType(); + Class getResultType(); + Class getFeedbackType(); + Class getFeedbackMessageType(); } diff --git a/rcljava_common/src/main/java/org/ros2/rcljava/interfaces/FeedbackDefinition.java b/rcljava_common/src/main/java/org/ros2/rcljava/interfaces/FeedbackDefinition.java new file mode 100644 index 00000000..daff4162 --- /dev/null +++ b/rcljava_common/src/main/java/org/ros2/rcljava/interfaces/FeedbackDefinition.java @@ -0,0 +1,25 @@ +/* Copyright 2021 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. + */ + +package org.ros2.rcljava.interfaces; + +/** +* Definition of an action feedback. +* Implementation of this interface for an Action is automatically generated. +* +* Don't extend this interface yourself!! +*/ +public interface FeedbackDefinition extends MessageDefinition { +} diff --git a/rcljava_common/src/main/java/org/ros2/rcljava/interfaces/FeedbackMessageDefinition.java b/rcljava_common/src/main/java/org/ros2/rcljava/interfaces/FeedbackMessageDefinition.java new file mode 100644 index 00000000..8ef39114 --- /dev/null +++ b/rcljava_common/src/main/java/org/ros2/rcljava/interfaces/FeedbackMessageDefinition.java @@ -0,0 +1,29 @@ +/* Copyright 2021 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. + */ + +package org.ros2.rcljava.interfaces; + +import java.util.List; + +/** +* Definition of an action feedback message. +* Implementation of this interface for an Action is automatically generated. +* +* Don't extend this interface yourself!! +*/ +public interface FeedbackMessageDefinition extends MessageDefinition { + void setFeedback(FeedbackDefinition feedback); + void setGoalUuid(List goalUuid); +} diff --git a/rcljava_common/src/main/java/org/ros2/rcljava/interfaces/GoalDefinition.java b/rcljava_common/src/main/java/org/ros2/rcljava/interfaces/GoalDefinition.java new file mode 100644 index 00000000..3aeea1af --- /dev/null +++ b/rcljava_common/src/main/java/org/ros2/rcljava/interfaces/GoalDefinition.java @@ -0,0 +1,25 @@ +/* Copyright 2021 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. + */ + +package org.ros2.rcljava.interfaces; + +/** +* Definition of an action goal. +* Implementation of this interface for an Action is automatically generated. +* +* Don't extend this interface yourself!! +*/ +public interface GoalDefinition extends MessageDefinition { +} diff --git a/rcljava_common/src/main/java/org/ros2/rcljava/interfaces/GoalRequestDefinition.java b/rcljava_common/src/main/java/org/ros2/rcljava/interfaces/GoalRequestDefinition.java index cf6dc177..5191a4ab 100644 --- a/rcljava_common/src/main/java/org/ros2/rcljava/interfaces/GoalRequestDefinition.java +++ b/rcljava_common/src/main/java/org/ros2/rcljava/interfaces/GoalRequestDefinition.java @@ -18,6 +18,6 @@ import java.util.List; public interface GoalRequestDefinition extends MessageDefinition { - MessageDefinition getGoal(); + GoalDefinition getGoal(); List getGoalUuid(); } diff --git a/rcljava_common/src/main/java/org/ros2/rcljava/interfaces/ResultDefinition.java b/rcljava_common/src/main/java/org/ros2/rcljava/interfaces/ResultDefinition.java new file mode 100644 index 00000000..daec4304 --- /dev/null +++ b/rcljava_common/src/main/java/org/ros2/rcljava/interfaces/ResultDefinition.java @@ -0,0 +1,25 @@ +/* Copyright 2021 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. + */ + +package org.ros2.rcljava.interfaces; + +/** +* Definition of an action result. +* Implementation of this interface for an Action is automatically generated. +* +* Don't extend this interface yourself!! +*/ +public interface ResultDefinition extends MessageDefinition { +} diff --git a/rcljava_common/src/main/java/org/ros2/rcljava/interfaces/ResultRequestDefinition.java b/rcljava_common/src/main/java/org/ros2/rcljava/interfaces/ResultRequestDefinition.java new file mode 100644 index 00000000..f259e1e7 --- /dev/null +++ b/rcljava_common/src/main/java/org/ros2/rcljava/interfaces/ResultRequestDefinition.java @@ -0,0 +1,22 @@ +/* Copyright 2021 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. + */ + +package org.ros2.rcljava.interfaces; + +import java.util.List; + +public interface ResultRequestDefinition extends MessageDefinition { + List getGoalUuid(); +} diff --git a/rcljava_common/src/main/java/org/ros2/rcljava/interfaces/ResultResponseDefinition.java b/rcljava_common/src/main/java/org/ros2/rcljava/interfaces/ResultResponseDefinition.java new file mode 100644 index 00000000..86a927e7 --- /dev/null +++ b/rcljava_common/src/main/java/org/ros2/rcljava/interfaces/ResultResponseDefinition.java @@ -0,0 +1,23 @@ +/* Copyright 2021 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. + */ + +package org.ros2.rcljava.interfaces; + +import java.util.List; + +public interface ResultResponseDefinition extends MessageDefinition { + void setResult(ResultDefinition result); + void setGoalStatus(byte status); +} diff --git a/rosidl_generator_java/resource/action.java.em b/rosidl_generator_java/resource/action.java.em index 2656f192..f5e33b2b 100644 --- a/rosidl_generator_java/resource/action.java.em +++ b/rosidl_generator_java/resource/action.java.em @@ -23,7 +23,10 @@ data = { 'output_dir': output_dir, 'template_basepath': template_basepath, } -data.update({'message': action.goal}) +data.update({ + 'message': action.goal, + 'marker_interfaces': [f'org.ros2.rcljava.interfaces.GoalDefinition<{type_name}>'], +}) output_file = os.path.join(output_dir, *namespaces[1:], goal_type_name + '.java') expand_template( 'msg.java.em', @@ -31,7 +34,10 @@ expand_template( output_file, template_basepath=template_basepath) -data.update({'message': action.result}) +data.update({ + 'message': action.result, + 'marker_interfaces': [f'org.ros2.rcljava.interfaces.ResultDefinition<{type_name}>'], +}) output_file = os.path.join(output_dir, *namespaces[1:], result_type_name + '.java') expand_template( 'msg.java.em', @@ -39,7 +45,10 @@ expand_template( output_file, template_basepath=template_basepath) -data.update({'message': action.feedback}) +data.update({ + 'message': action.feedback, + 'marker_interfaces': [f'org.ros2.rcljava.interfaces.FeedbackDefinition<{type_name}>'], +}) output_file = os.path.join(output_dir, *namespaces[1:], feedback_type_name + '.java') expand_template( 'msg.java.em', @@ -47,7 +56,10 @@ expand_template( output_file, template_basepath=template_basepath) -data.update({'message': action.feedback_message}) +data.update({ + 'message': action.feedback_message, + 'marker_interfaces': [], +}) output_file = os.path.join(output_dir, *namespaces[1:], feedback_message_type_name + '.java') expand_template( 'msg.java.em', @@ -55,6 +67,8 @@ expand_template( output_file, template_basepath=template_basepath) +del data['marker_interfaces'] +del data['message'] data.update({'service': action.send_goal_service}) output_file = os.path.join(output_dir, *namespaces[1:], send_goal_type_name + '.java') expand_template( @@ -75,9 +89,15 @@ action_imports = [ 'java.util.List', 'org.ros2.rcljava.common.JNIUtils', 'org.ros2.rcljava.interfaces.ActionDefinition', + 'org.ros2.rcljava.interfaces.FeedbackDefinition', + 'org.ros2.rcljava.interfaces.FeedbackMessageDefinition', + 'org.ros2.rcljava.interfaces.GoalDefinition', 'org.ros2.rcljava.interfaces.GoalRequestDefinition', 'org.ros2.rcljava.interfaces.GoalResponseDefinition', 'org.ros2.rcljava.interfaces.MessageDefinition', + 'org.ros2.rcljava.interfaces.ResultDefinition', + 'org.ros2.rcljava.interfaces.ResultRequestDefinition', + 'org.ros2.rcljava.interfaces.ResultResponseDefinition', 'org.slf4j.Logger', 'org.slf4j.LoggerFactory', ] @@ -91,7 +111,6 @@ public class @(type_name) implements ActionDefinition { public static class SendGoalRequest extends @(type_name)_SendGoal_Request implements GoalRequestDefinition<@(type_name)> { public List getGoalUuid() { - // Return List since it's hash is based on the values (not the object pointer) return super.getGoalId().getUuidAsList(); } } @@ -109,20 +128,56 @@ public class @(type_name) implements ActionDefinition { } } - public Class getSendGoalRequestType() { + public static class GetResultRequest extends @(type_name)_GetResult_Request implements ResultRequestDefinition<@(type_name)> { + public List getGoalUuid() { + return super.getGoalId().getUuidAsList(); + } + } + + public static class GetResultResponse extends @(type_name)_GetResult_Response implements ResultResponseDefinition<@(type_name)> { + public void setResult(ResultDefinition<@(type_name)> result) { + super.setResult((@(type_name)_Result)result); + } + public void setGoalStatus(byte status) { + super.setStatus(status); + } + } + + public static class FeedbackMessage extends @(type_name)_FeedbackMessage implements FeedbackMessageDefinition<@(type_name)> { + public void setFeedback(FeedbackDefinition<@(type_name)> feedback) { + super.setFeedback((@(type_name)_Feedback) feedback); + } + public void setGoalUuid(List goalUuid) { + super.getGoalId().setUuid(goalUuid); + } + } + + public Class> getSendGoalRequestType() { return SendGoalRequest.class; } - public Class getSendGoalResponseType() { + public Class> getSendGoalResponseType() { return SendGoalResponse.class; } - public Class getGetResultRequestType() { - return @(type_name)_GetResult_Request.class; + public Class> getGetResultRequestType() { + return GetResultRequest.class; + } + + public Class> getGetResultResponseType() { + return GetResultResponse.class; } - public Class getGetResultResponseType() { - return @(type_name)_GetResult_Response.class; + public Class> getResultType() { + return @(type_name)_Result.class; + } + + public Class> getFeedbackType() { + return @(type_name)_Feedback.class; + } + + public Class> getFeedbackMessageType() { + return FeedbackMessage.class; } private static final Logger logger = LoggerFactory.getLogger(@(type_name).class); @@ -143,6 +198,4 @@ public class @(type_name) implements ActionDefinition { public static final Class<@(type_name)_Result> ResultType = @(type_name)_Result.class; public static final Class<@(type_name)_Feedback> FeedbackType = @(type_name)_Feedback.class; - - public static final Class<@(type_name)_FeedbackMessage> FeedbackMessageType = @(type_name)_FeedbackMessage.class; } diff --git a/rosidl_generator_java/resource/idl.java.em b/rosidl_generator_java/resource/idl.java.em index f34d75c7..9c90c354 100644 --- a/rosidl_generator_java/resource/idl.java.em +++ b/rosidl_generator_java/resource/idl.java.em @@ -26,6 +26,7 @@ data = { 'interface_path': interface_path, 'output_dir': output_dir, 'template_basepath': template_basepath, + 'marker_interfaces': [], } for message in content.get_elements_of_type(Message): diff --git a/rosidl_generator_java/resource/msg.java.em b/rosidl_generator_java/resource/msg.java.em index 9dcb2a42..f5b62f2e 100644 --- a/rosidl_generator_java/resource/msg.java.em +++ b/rosidl_generator_java/resource/msg.java.em @@ -17,6 +17,9 @@ from rosidl_parser.definition import BoundedSequence from rosidl_parser.definition import NamespacedType type_name = message.structure.namespaced_type.name +interfaces_implemented = ['MessageDefinition'] +interfaces_implemented.extend(f"{t.rsplit('.', 1)[1]}" for t in marker_interfaces) +interfaces_implemented = ', '.join(interfaces_implemented) message_imports = [ 'org.apache.commons.lang3.builder.EqualsBuilder', @@ -26,6 +29,7 @@ message_imports = [ 'org.slf4j.Logger', 'org.slf4j.LoggerFactory', ] +message_imports.extend(f"{t.split('<', 1)[0]}" for t in marker_interfaces) }@ @[for message_import in message_imports]@ import @(message_import); @@ -38,7 +42,7 @@ import @('.'.join(member.type.namespaced_name())); @[ end if]@ @[end for]@ -public class @(type_name) implements MessageDefinition { +public class @(type_name) implements @(interfaces_implemented) { private static final Logger logger = LoggerFactory.getLogger(@(type_name).class); diff --git a/rosidl_generator_java/resource/srv.java.em b/rosidl_generator_java/resource/srv.java.em index 9be7a6a7..8bfb887c 100644 --- a/rosidl_generator_java/resource/srv.java.em +++ b/rosidl_generator_java/resource/srv.java.em @@ -18,6 +18,7 @@ data = { 'interface_path': interface_path, 'output_dir': output_dir, 'template_basepath': template_basepath, + 'marker_interfaces': [], } data.update({'message': service.request_message}) output_file = os.path.join(output_dir, *namespaces[1:], request_type_name + '.java')