diff --git a/rclpy/rclpy/node.py b/rclpy/rclpy/node.py index 07914e3f5..f8cc0dd48 100644 --- a/rclpy/rclpy/node.py +++ b/rclpy/rclpy/node.py @@ -148,8 +148,8 @@ def __init__( self.__guards: List[GuardCondition] = [] self.__waitables: List[Waitable] = [] self._default_callback_group = MutuallyExclusiveCallbackGroup() + self._parameters_callbacks: List[Callable[[List[Parameter]], SetParametersResult]] = [] self._rate_group = ReentrantCallbackGroup() - self._parameters_callback = None self._allow_undeclared_parameters = allow_undeclared_parameters self._parameter_overrides = {} self._descriptors = {} @@ -525,7 +525,7 @@ def get_parameter_or( return self._parameters.get(name, alternative_value) - def get_parameters_by_prefix(self, prefix: str) -> Dict[str, Parameter]: + def get_parameters_by_prefix(self, prefix: str) -> List[Parameter]: """ Get parameters that have a given prefix in their names as a dictionary. @@ -721,10 +721,12 @@ def _set_parameters_atomically( if not result.successful: return result - elif self._parameters_callback: - result = self._parameters_callback(parameter_list) - else: - result = SetParametersResult(successful=True) + elif self._parameters_callbacks: + for callback in self._parameters_callbacks: + result = callback(parameter_list) + if not result.successful: + return result + result = SetParametersResult(successful=True) if result.successful: parameter_event = ParameterEvent() @@ -768,6 +770,33 @@ def _set_parameters_atomically( return result + def add_on_set_parameters_callback( + self, + callback: Callable[[List[Parameter]], SetParametersResult] + ) -> None: + """ + Add a callback in front to the list of callbacks. + + Calling this function will add a callback in self._parameter_callbacks list. + + :param callback: The function that is called whenever parameters are set for the node. + """ + self._parameters_callbacks.insert(0, callback) + + def remove_on_set_parameters_callback( + self, + callback: Callable[[List[Parameter]], SetParametersResult] + ) -> None: + """ + Remove a callback from list of callbacks. + + Calling this function will remove the callback from self._parameter_callbacks list. + + :param callback: The function that is called whenever parameters are set for the node. + :raises: ValueError if a callback is not present in the list of callbacks. + """ + self._parameters_callbacks.remove(callback) + def _apply_descriptors( self, parameter_list: List[Parameter], @@ -1026,11 +1055,11 @@ def set_parameters_callback( """ Register a set parameters callback. - Calling this function with override any previously registered callback. + Calling this function will add a callback to the self._parameter_callbacks list. :param callback: The function that is called whenever parameters are set for the node. """ - self._parameters_callback = callback + self._parameters_callbacks = [callback] def _validate_topic_or_service_name(self, topic_or_service_name, *, is_service=False): name = self.get_name() diff --git a/rclpy/test/test_node.py b/rclpy/test/test_node.py index 6fd9c4e52..b699871ce 100644 --- a/rclpy/test/test_node.py +++ b/rclpy/test/test_node.py @@ -587,6 +587,11 @@ def reject_parameter_callback(self, parameter_list): rejected_parameters = (param for param in parameter_list if 'reject' in param.name) return SetParametersResult(successful=(not any(rejected_parameters))) + def reject_parameter_callback_1(self, parameter_list): + rejected_parameters = ( + param for param in parameter_list if 'refuse' in param.name) + return SetParametersResult(successful=(not any(rejected_parameters))) + def test_use_sim_time(self): self.assertTrue(self.node.has_parameter(USE_SIM_TIME_NAME)) self.assertFalse(self.node.get_parameter(USE_SIM_TIME_NAME).value) @@ -823,7 +828,6 @@ def test_node_set_parameters_rejection(self): True, ParameterDescriptor() ) - self.node.declare_parameter(*reject_parameter_tuple) self.node.set_parameters_callback(self.reject_parameter_callback) result = self.node.set_parameters( @@ -838,6 +842,130 @@ def test_node_set_parameters_rejection(self): self.assertIsInstance(result[0], SetParametersResult) self.assertFalse(result[0].successful) + def test_node_set_parameters_rejection_list(self): + # Declare a new parameters and set a list of callbacks so that it's rejected when set. + reject_list_parameter_tuple = [ + ('reject', True, ParameterDescriptor()), + ('accept', True, ParameterDescriptor()), + ('accept', True, ParameterDescriptor()) + ] + self.node.declare_parameters('', reject_list_parameter_tuple) + self.node.add_on_set_parameters_callback(self.reject_parameter_callback) + self.node.add_on_set_parameters_callback(self.reject_parameter_callback_1) + + result = self.node.set_parameters( + [ + Parameter( + name=reject_list_parameter_tuple[0][0], + value=reject_list_parameter_tuple[0][1] + ), + Parameter( + name=reject_list_parameter_tuple[1][0], + value=reject_list_parameter_tuple[1][1] + ), + Parameter( + name=reject_list_parameter_tuple[2][0], + value=reject_list_parameter_tuple[2][1] + ) + ] + ) + self.assertEqual(3, len(result)) + self.assertIsInstance(result, list) + self.assertIsInstance(result[0], SetParametersResult) + self.assertFalse(result[0].successful) + self.assertIsInstance(result[1], SetParametersResult) + self.assertTrue(result[1].successful) + self.assertIsInstance(result[2], SetParametersResult) + self.assertTrue(result[2].successful) + + def test_node_add_on_set_parameter_callback(self): + # Add callbacks to the list of callbacks. + callbacks = [ + self.reject_parameter_callback, + self.reject_parameter_callback_1 + ] + for callback in callbacks: + self.node.add_on_set_parameters_callback(callback) + for callback in callbacks: + self.assertTrue(callback in self.node._parameters_callbacks) + for callback in callbacks: + self.node.remove_on_set_parameters_callback(callback) + + # Adding the parameters which will be accepted without any rejections. + non_reject_parameter_tuple = [ + ('accept_1', True, ParameterDescriptor()), + ('accept_2', True, ParameterDescriptor()) + ] + self.node.declare_parameters('', non_reject_parameter_tuple) + + for callback in callbacks: + self.node.add_on_set_parameters_callback(callback) + + result = self.node.set_parameters( + [ + Parameter( + name=non_reject_parameter_tuple[0][0], + value=non_reject_parameter_tuple[0][1] + ), + Parameter( + name=non_reject_parameter_tuple[1][0], + value=non_reject_parameter_tuple[1][1] + ) + ] + ) + self.assertEqual(2, len(result)) + self.assertIsInstance(result, list) + self.assertIsInstance(result[0], SetParametersResult) + self.assertTrue(result[0].successful) + self.assertIsInstance(result[1], SetParametersResult) + self.assertTrue(result[1].successful) + + def test_node_remove_from_set_callback(self): + # Remove callbacks from list of callbacks. + parameter_tuple = ( + 'refuse', True, ParameterDescriptor() + ) + self.node.declare_parameter(*parameter_tuple) + + callbacks = [ + self.reject_parameter_callback_1, + ] + # Checking if the callbacks are not already present. + for callback in callbacks: + self.assertFalse(callback in self.node._parameters_callbacks) + + for callback in callbacks: + self.node.add_on_set_parameters_callback(callback) + + result = self.node.set_parameters( + [ + Parameter( + name=parameter_tuple[0], + value=parameter_tuple[1] + ) + ] + ) + self.assertEqual(1, len(result)) + self.assertIsInstance(result, list) + self.assertIsInstance(result[0], SetParametersResult) + self.assertFalse(result[0].successful) + # Removing the callback which is causing the rejection. + self.node.remove_on_set_parameters_callback(self.reject_parameter_callback_1) + self.assertFalse(self.reject_parameter_callback_1 in self.node._parameters_callbacks) + # Now the setting its value again. + result = self.node.set_parameters( + [ + Parameter( + name=parameter_tuple[0], + value=parameter_tuple[1] + ) + ] + ) + self.assertEqual(1, len(result)) + self.assertIsInstance(result, list) + self.assertIsInstance(result[0], SetParametersResult) + self.assertTrue(result[0].successful) + def test_node_set_parameters_read_only(self): integer_value = 42 string_value = 'hello'