diff --git a/rcljava/CMakeLists.txt b/rcljava/CMakeLists.txt index dda754a0..ce23a1a7 100644 --- a/rcljava/CMakeLists.txt +++ b/rcljava/CMakeLists.txt @@ -153,6 +153,7 @@ set(${PROJECT_NAME}_sources "src/main/java/org/ros2/rcljava/publisher/PublisherImpl.java" "src/main/java/org/ros2/rcljava/qos/policies/Durability.java" "src/main/java/org/ros2/rcljava/qos/policies/History.java" + "src/main/java/org/ros2/rcljava/qos/policies/Liveliness.java" "src/main/java/org/ros2/rcljava/qos/policies/QoSPolicy.java" "src/main/java/org/ros2/rcljava/qos/policies/Reliability.java" "src/main/java/org/ros2/rcljava/qos/QoSProfile.java" @@ -237,6 +238,7 @@ if(BUILD_TESTING) # "src/test/java/org/ros2/rcljava/parameters/AsyncParametersClientTest.java" # "src/test/java/org/ros2/rcljava/parameters/SyncParametersClientTest.java" "src/test/java/org/ros2/rcljava/publisher/PublisherTest.java" + "src/test/java/org/ros2/rcljava/qos/QoSProfileTest.java" "src/test/java/org/ros2/rcljava/subscription/SubscriptionTest.java" "src/test/java/org/ros2/rcljava/timer/TimerTest.java" ) @@ -252,6 +254,7 @@ if(BUILD_TESTING) "org.ros2.rcljava.node.NodeTest" # "org.ros2.rcljava.parameters.SyncParametersClientTest" "org.ros2.rcljava.publisher.PublisherTest" + "org.ros2.rcljava.qos.QoSProfileTest" "org.ros2.rcljava.subscription.SubscriptionTest" "org.ros2.rcljava.timer.TimerTest" ) diff --git a/rcljava/include/org_ros2_rcljava_RCLJava.h b/rcljava/include/org_ros2_rcljava_RCLJava.h index 4d8d1b50..85859eb7 100644 --- a/rcljava/include/org_ros2_rcljava_RCLJava.h +++ b/rcljava/include/org_ros2_rcljava_RCLJava.h @@ -52,7 +52,8 @@ JNICALL Java_org_ros2_rcljava_RCLJava_nativeGetRMWIdentifier(JNIEnv *, jclass); */ JNIEXPORT jlong JNICALL Java_org_ros2_rcljava_RCLJava_nativeConvertQoSProfileToHandle( - JNIEnv *, jclass, jint, jint, jint, jint, jboolean); + JNIEnv *, jclass, + jint, jint, jint, jint, jlong, jint, jlong, jint, jint, jlong, jint, jboolean); /* * Class: org_ros2_rcljava_RCLJava diff --git a/rcljava/src/main/cpp/org_ros2_rcljava_RCLJava.cpp b/rcljava/src/main/cpp/org_ros2_rcljava_RCLJava.cpp index 1a3b640d..957329d9 100644 --- a/rcljava/src/main/cpp/org_ros2_rcljava_RCLJava.cpp +++ b/rcljava/src/main/cpp/org_ros2_rcljava_RCLJava.cpp @@ -31,6 +31,7 @@ #include "org_ros2_rcljava_RCLJava.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::convert_to_java_signature; @@ -152,9 +153,31 @@ Java_org_ros2_rcljava_RCLJava_nativeGetRMWIdentifier(JNIEnv * env, jclass) return env->NewStringUTF(rmw_implementation_identifier); } +#define RCLJAVA_QOS_SET_RMW_TIME(qos_profile, policy_name, seconds, nanos) \ + do { \ + if (seconds < 0) { \ + rcljava_throw_exception( \ + env, "java/lang/IllegalArgumentException", \ + "seconds must not be negative for " #policy_name); \ + free(qos_profile); \ + return 0; \ + } \ + if (nanos < 0) { \ + rcljava_throw_exception( \ + env, "java/lang/IllegalArgumentException", \ + "nanoseconds must not be negative for " #policy_name); \ + free(qos_profile); \ + return 0; \ + } \ + qos_profile->policy_name.sec = static_cast(seconds); \ + qos_profile->policy_name.nsec = static_cast(nanos); \ + } while (0) + JNIEXPORT jlong JNICALL Java_org_ros2_rcljava_RCLJava_nativeConvertQoSProfileToHandle( - JNIEnv *, jclass, jint history, jint depth, jint reliability, jint durability, + JNIEnv * env, jclass, jint history, jint depth, jint reliability, jint durability, + jlong deadline_sec, jint deadline_nanos, jlong lifespan_sec, jint lifespan_nanos, + jint liveliness, jlong liveliness_lease_sec, jint liveliness_lease_nanos, jboolean avoidROSNamespaceConventions) { rmw_qos_profile_t * qos_profile = @@ -163,11 +186,11 @@ Java_org_ros2_rcljava_RCLJava_nativeConvertQoSProfileToHandle( qos_profile->depth = depth; qos_profile->reliability = static_cast(reliability); qos_profile->durability = static_cast(durability); - // TODO(jacobperron): Expose deadline, lifespan, and liveliness settings as parameters - qos_profile->deadline = rmw_qos_profile_default.deadline; - qos_profile->lifespan = rmw_qos_profile_default.lifespan; - qos_profile->liveliness = rmw_qos_profile_default.liveliness; - qos_profile->liveliness_lease_duration = rmw_qos_profile_default.liveliness_lease_duration; + RCLJAVA_QOS_SET_RMW_TIME(qos_profile, deadline, deadline_sec, deadline_nanos); + RCLJAVA_QOS_SET_RMW_TIME(qos_profile, lifespan, lifespan_sec, lifespan_nanos); + qos_profile->liveliness = static_cast(liveliness); + RCLJAVA_QOS_SET_RMW_TIME( + qos_profile, liveliness_lease_duration, liveliness_lease_sec, liveliness_lease_nanos); qos_profile->avoid_ros_namespace_conventions = avoidROSNamespaceConventions; return reinterpret_cast(qos_profile); } diff --git a/rcljava/src/main/java/org/ros2/rcljava/RCLJava.java b/rcljava/src/main/java/org/ros2/rcljava/RCLJava.java index bb0ccd20..635fdd2b 100644 --- a/rcljava/src/main/java/org/ros2/rcljava/RCLJava.java +++ b/rcljava/src/main/java/org/ros2/rcljava/RCLJava.java @@ -318,18 +318,22 @@ public static synchronized void shutdown() { } public static long convertQoSProfileToHandle(final QoSProfile qosProfile) { - int history = qosProfile.getHistory().getValue(); - int depth = qosProfile.getDepth(); - int reliability = qosProfile.getReliability().getValue(); - int durability = qosProfile.getDurability().getValue(); - boolean avoidROSNamespaceConventions = qosProfile.getAvoidROSNamespaceConventions(); - return nativeConvertQoSProfileToHandle( - history, depth, reliability, durability, avoidROSNamespaceConventions); + qosProfile.getHistory().getValue(), + qosProfile.getDepth(), + qosProfile.getReliability().getValue(), + qosProfile.getDurability().getValue(), + qosProfile.getDeadline().getSeconds(), qosProfile.getDeadline().getNano(), + qosProfile.getLifespan().getSeconds(), qosProfile.getLifespan().getNano(), + qosProfile.getLiveliness().getValue(), qosProfile.getLivelinessLeaseDuration().getSeconds(), + qosProfile.getLivelinessLeaseDuration().getNano(), + qosProfile.getAvoidROSNamespaceConventions()); } - private static native long nativeConvertQoSProfileToHandle(int history, int depth, - int reliability, int durability, boolean avoidROSNamespaceConventions); + private static native long nativeConvertQoSProfileToHandle( + int history, int depth, int reliability, int durability, long deadlineSec, int deadlineNanos, + long lifespanSec, int lifespanNanos, int liveliness, long livelinessLeaseSec, + int livelinessLeaseNanos, boolean avoidROSNamespaceConventions); public static void disposeQoSProfile(final long qosProfileHandle) { nativeDisposeQoSProfile(qosProfileHandle); diff --git a/rcljava/src/main/java/org/ros2/rcljava/qos/QoSProfile.java b/rcljava/src/main/java/org/ros2/rcljava/qos/QoSProfile.java index 8b8a4c71..15b81712 100644 --- a/rcljava/src/main/java/org/ros2/rcljava/qos/QoSProfile.java +++ b/rcljava/src/main/java/org/ros2/rcljava/qos/QoSProfile.java @@ -15,21 +15,38 @@ package org.ros2.rcljava.qos; +import java.time.Duration; + import org.ros2.rcljava.qos.policies.Durability; import org.ros2.rcljava.qos.policies.History; +import org.ros2.rcljava.qos.policies.Liveliness; import org.ros2.rcljava.qos.policies.QoSPolicy; import org.ros2.rcljava.qos.policies.Reliability; public class QoSProfile { - private final History history; + // TODO(ivanpauno): Update all qos policies in a way that the objects are created from RCL, + // to avoid depending on the enum values. + private History history; + + private int depth; + + private Reliability reliability; + + private Durability durability; - private final int depth; + private Duration deadline = Duration.ofSeconds(0, 0); - private final Reliability reliability; + private Duration lifespan = Duration.ofSeconds(0, 0); - private final Durability durability; + private Liveliness liveliness = Liveliness.SYSTEM_DEFAULT; - private final boolean avoidROSNamespaceConventions; + private Duration livelinessLeaseDuration = Duration.ofSeconds(0, 0); + + private boolean avoidROSNamespaceConventions; + + public QoSProfile(int depth) { + this(History.KEEP_LAST, depth, Reliability.RELIABLE, Durability.VOLATILE, false); + } public QoSProfile(History history, int depth, Reliability reliability, Durability durability, boolean avoidROSNamespaceConventions) { @@ -44,39 +61,127 @@ public final History getHistory() { return this.history; } + public final QoSProfile setHistory(final History history) { + this.history = history; + return this; + } + public final int getDepth() { return this.depth; } + public final QoSProfile setDepth(final int depth) { + this.depth = depth; + return this; + } + public final Reliability getReliability() { return this.reliability; } + public final QoSProfile setReliability(final Reliability reliability) { + this.reliability = reliability; + return this; + } + public final Durability getDurability() { return this.durability; } + public final QoSProfile setDurability(final Durability durability) { + this.durability = durability; + return this; + } + + public final Duration getDeadline() { + return this.deadline; + } + + public final QoSProfile setDeadline(final Duration deadline) { + this.deadline = deadline; + return this; + } + + public final Duration getLifespan() { + return this.lifespan; + } + + public final QoSProfile setLifespan(final Duration lifespan) { + this.lifespan = lifespan; + return this; + } + + public final Liveliness getLiveliness() { + return this.liveliness; + } + + public final QoSProfile setLiveliness(final Liveliness liveliness) { + this.liveliness = liveliness; + return this; + } + + public final Duration getLivelinessLeaseDuration() { + return this.livelinessLeaseDuration; + } + + public final QoSProfile setLivelinessLeaseDuration(final Duration livelinessLeaseDuration) { + this.livelinessLeaseDuration = livelinessLeaseDuration; + return this; + } + public final boolean getAvoidROSNamespaceConventions() { return this.avoidROSNamespaceConventions; } + public final QoSProfile setAvoidROSNamespaceConventions(final boolean avoidROSConventions) { + this.avoidROSNamespaceConventions = avoidROSConventions; + return this; + } + + // TODO(ivanpauno): refactor all static default profiles methods, + // so that the return value is get from the rmw definition directly + // (leveraging a native function). + public static final QoSProfile defaultProfile() { + return new QoSProfile(10); + } + + public static final QoSProfile systemDefault() { + return new QoSProfile( + History.SYSTEM_DEFAULT, QoSProfile.DEPTH_SYSTEM_DEFAULT, + Reliability.SYSTEM_DEFAULT, Durability.SYSTEM_DEFAULT, false); + } + + public static final QoSProfile sensorData() { + return new QoSProfile( + History.KEEP_LAST, 5, Reliability.BEST_EFFORT, Durability.VOLATILE, false); + } + + public static final QoSProfile parametersDefault() { + return new QoSProfile( + History.KEEP_LAST, 1000, Reliability.RELIABLE, Durability.VOLATILE, false); + } + + public static final QoSProfile servicesDefault() { + return new QoSProfile( + History.KEEP_LAST, 10, Reliability.RELIABLE, Durability.VOLATILE, false); + } + + public static final QoSProfile parameterEventsDefault() { + return new QoSProfile( + History.KEEP_ALL, 1000, Reliability.RELIABLE, Durability.VOLATILE, false); + } + public static final int DEPTH_SYSTEM_DEFAULT = 0; - public static final QoSProfile SENSOR_DATA = - new QoSProfile(History.KEEP_LAST, 5, Reliability.BEST_EFFORT, Durability.VOLATILE, false); + public static final QoSProfile SENSOR_DATA = sensorData(); - public static final QoSProfile PARAMETERS = - new QoSProfile(History.KEEP_LAST, 1000, Reliability.RELIABLE, Durability.VOLATILE, false); + public static final QoSProfile PARAMETERS = parametersDefault(); - public static final QoSProfile DEFAULT = - new QoSProfile(History.KEEP_LAST, 10, Reliability.RELIABLE, Durability.VOLATILE, false); + public static final QoSProfile DEFAULT = defaultProfile(); - public static final QoSProfile SERVICES_DEFAULT = - new QoSProfile(History.KEEP_LAST, 10, Reliability.RELIABLE, Durability.VOLATILE, false); + public static final QoSProfile SERVICES_DEFAULT = servicesDefault(); - public static final QoSProfile PARAMETER_EVENTS = - new QoSProfile(History.KEEP_ALL, 1000, Reliability.RELIABLE, Durability.VOLATILE, false); + public static final QoSProfile PARAMETER_EVENTS = parameterEventsDefault(); - public static final QoSProfile SYSTEM_DEFAULT = new QoSProfile(History.SYSTEM_DEFAULT, - DEPTH_SYSTEM_DEFAULT, Reliability.SYSTEM_DEFAULT, Durability.SYSTEM_DEFAULT, false); + public static final QoSProfile SYSTEM_DEFAULT = systemDefault(); } diff --git a/rcljava/src/main/java/org/ros2/rcljava/qos/policies/Liveliness.java b/rcljava/src/main/java/org/ros2/rcljava/qos/policies/Liveliness.java new file mode 100644 index 00000000..0a814e7f --- /dev/null +++ b/rcljava/src/main/java/org/ros2/rcljava/qos/policies/Liveliness.java @@ -0,0 +1,32 @@ +/* Copyright 2020 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.qos.policies; + +public enum Liveliness implements QoSPolicy { + SYSTEM_DEFAULT(0), + AUTOMATIC(1), + MANUAL_BY_TOPIC(3); + + private final int value; + + Liveliness(final int value) { + this.value = value; + } + + public int getValue() { + return value; + } +} diff --git a/rcljava/src/test/java/org/ros2/rcljava/qos/QoSProfileTest.java b/rcljava/src/test/java/org/ros2/rcljava/qos/QoSProfileTest.java new file mode 100644 index 00000000..f2414e7b --- /dev/null +++ b/rcljava/src/test/java/org/ros2/rcljava/qos/QoSProfileTest.java @@ -0,0 +1,60 @@ +/* Copyright 2020 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.qos; + +import static org.junit.Assert.assertEquals; + +import org.junit.BeforeClass; +import org.junit.Test; + +import org.ros2.rcljava.qos.QoSProfile; +import org.ros2.rcljava.qos.policies.Durability; +import org.ros2.rcljava.qos.policies.History; +import org.ros2.rcljava.qos.policies.Reliability; + +public class QoSProfileTest { + @BeforeClass + public static void setupOnce() throws Exception { + // Just to quiet down warnings + org.apache.log4j.BasicConfigurator.configure(); + } + + @Test + public final void testQoSProfileBasic() { + QoSProfile qosProfile = new QoSProfile( + History.KEEP_LAST, 1, Reliability.RELIABLE, Durability.VOLATILE, false); + assertEquals(qosProfile.getHistory(), History.KEEP_LAST); + assertEquals(qosProfile.getDepth(), 1); + assertEquals(qosProfile.getReliability(), Reliability.RELIABLE); + assertEquals(qosProfile.getDurability(), Durability.VOLATILE); + assertEquals(qosProfile.getAvoidROSNamespaceConventions(), false); + } + + @Test + public final void testQoSProfileChaining() { + QoSProfile qosProfile = QoSProfile.defaultProfile() + .setHistory(History.KEEP_ALL) + .setDepth(11) + .setReliability(Reliability.BEST_EFFORT) + .setDurability(Durability.TRANSIENT_LOCAL) + .setAvoidROSNamespaceConventions(true); + assertEquals(qosProfile.getHistory(), History.KEEP_ALL); + assertEquals(qosProfile.getDepth(), 11); + assertEquals(qosProfile.getReliability(), Reliability.BEST_EFFORT); + assertEquals(qosProfile.getDurability(), Durability.TRANSIENT_LOCAL); + assertEquals(qosProfile.getAvoidROSNamespaceConventions(), true); + } +}