From 23af4e2a3db4d3c8e495fdd29ec64ec99a65ff9c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jaroslav=20Klap=C3=A1lek?= Date: Wed, 24 Aug 2022 13:46:53 +0200 Subject: [PATCH 1/8] Implement support for conditions with parameters --- autopsy/reconfigure.py | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/autopsy/reconfigure.py b/autopsy/reconfigure.py index 9505a63..b740f86 100644 --- a/autopsy/reconfigure.py +++ b/autopsy/reconfigure.py @@ -470,6 +470,15 @@ def __init__(self, name, default, *args, **kwargs): super(IntP, self).__init__(name, default, int, *args, **kwargs) + def __nonzero__(self): + """Used for evaluating conditions (Py2).""" + return self.value != 0 + + def __bool__(self): + """Used for evaluating conditions (Py3).""" + return self.value != 0 + + class DoubleP(ConstrainedP): """Parameter of a type float/double.""" @@ -477,6 +486,15 @@ def __init__(self, name, default, *args, **kwargs): super(DoubleP, self).__init__(name, default, float, *args, **kwargs) + def __nonzero__(self): + """Used for evaluating conditions (Py2).""" + return self.value != 0.0 + + def __bool__(self): + """Used for evaluating conditions (Py3).""" + return self.value != 0.0 + + class BoolP(Parameter): """Parameter of a type bool.""" @@ -487,6 +505,15 @@ def __init__(self, name, default, *args, **kwargs): super(BoolP, self).__init__(name, default, bool, *args, **kwargs) + def __nonzero__(self): + """Used for evaluating conditions (Py2).""" + return self.value + + def __bool__(self): + """Used for evaluating conditions (Py3).""" + return self.value + + class StrP(Parameter): """Parameter of a type string.""" @@ -497,6 +524,11 @@ def __init__(self, name, default, *args, **kwargs): super(StrP, self).__init__(name, default, str, *args, **kwargs) + def __len__(self): + """Size of the string. Also used for evaluating conditions.""" + return len(self.value) + + ###################### # ParameterDict ###################### From 30bdf8d013cad8c23a6568d7c1d2532bb3599323 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jaroslav=20Klap=C3=A1lek?= Date: Wed, 24 Aug 2022 13:48:20 +0200 Subject: [PATCH 2/8] Update readme and changelog --- CHANGELOG.md | 4 ++++ README.md | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7649840..23d6f86 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,10 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/). ## Unreleased +### Added +- `reconfigure`: + - Parameters can be now used within conditions. + ## 0.5.1 - 2022-03-23 ### Added - `reconfigure`: diff --git a/README.md b/README.md index 31e664d..17fbcc0 100644 --- a/README.md +++ b/README.md @@ -80,7 +80,7 @@ Upon assigning a value to parameter, an internal object is created. _Note: The `.value` is used only when you want to store it somewhere else._ -_Note: Features `is`, `not` and `bool()` are not currently available here._ +_Note: Feature `is` are not currently available here._ Example: ```python From e60799b953566ff8653b03f71bb5c8ecaa7e9135 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jaroslav=20Klap=C3=A1lek?= Date: Wed, 24 Aug 2022 13:57:23 +0200 Subject: [PATCH 3/8] Implement membership test operator contains --- CHANGELOG.md | 1 + autopsy/reconfigure.py | 9 +++++++++ 2 files changed, 10 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 23d6f86..506801f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/). ### Added - `reconfigure`: - Parameters can be now used within conditions. + - Implementation of `__contains__` to support `if ... in P`. ## 0.5.1 - 2022-03-23 ### Added diff --git a/autopsy/reconfigure.py b/autopsy/reconfigure.py index b740f86..6014b8c 100644 --- a/autopsy/reconfigure.py +++ b/autopsy/reconfigure.py @@ -868,6 +868,15 @@ def __setattr__(self, name, value): raise TypeError("Unable to create a parameter of type '%s'." % type(value)) + def __contains__(self, name): + """Check whether a parameter name exists. Used for 'if name in P'. + + Arguments: + name -- name of the parameter, str + """ + return name in self._parameters + + def update(self, parameters): """Updates the parameters according to the passed dictionary. From 6ad5c5feacfbe125dc240554b3be40e387d0b2b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jaroslav=20Klap=C3=A1lek?= Date: Wed, 24 Aug 2022 14:08:55 +0200 Subject: [PATCH 4/8] Add 'only_existing' argument to 'update()' --- CHANGELOG.md | 1 + README.md | 2 ++ autopsy/reconfigure.py | 9 ++++++--- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 506801f..0740e90 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/). - `reconfigure`: - Parameters can be now used within conditions. - Implementation of `__contains__` to support `if ... in P`. + - `update()` now takes optional argument `only_existing` (def. `False`) to only update existing parameters. ## 0.5.1 - 2022-03-23 ### Added diff --git a/README.md b/README.md index 17fbcc0..416329d 100644 --- a/README.md +++ b/README.md @@ -203,6 +203,8 @@ P.reconfigure() ``` _Note: Passing a string to `P.reconfigure()` changes the namespace of the ParameterServer in ROS._ +_Note: From `>0.5.1` an additional bool can be passed to `P.update()` to only update existing parameters._ + ### Compatibility diff --git a/autopsy/reconfigure.py b/autopsy/reconfigure.py index 6014b8c..2fc6ef3 100644 --- a/autopsy/reconfigure.py +++ b/autopsy/reconfigure.py @@ -877,18 +877,21 @@ def __contains__(self, name): return name in self._parameters - def update(self, parameters): + def update(self, parameters, only_existing = False): """Updates the parameters according to the passed dictionary. Arguments: parameters -- new values of the parameters, dict(str, any) or list(tuple(str, any)) + only_existing -- when True only update values, do not add new parameters, bool, default False """ if isinstance(parameters, dict): for param, value in parameters.items(): - self.__setattr__(param, value) + if not only_existing or param in self: + self.__setattr__(param, value) elif isinstance(parameters, list): for param, value in parameters: - self.__setattr__(param, value) + if not only_existing or param in self: + self.__setattr__(param, value) else: raise NotImplementedError("ParameterServer.update() is not supported for type '%s'." % type(parameters)) From e4a68180a2c2bf0db0f341502c6897ab00886bd9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jaroslav=20Klap=C3=A1lek?= Date: Wed, 24 Aug 2022 14:22:52 +0200 Subject: [PATCH 5/8] Save reconfigure node for later usage --- autopsy/reconfigure.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/autopsy/reconfigure.py b/autopsy/reconfigure.py index 2fc6ef3..8d7457d 100644 --- a/autopsy/reconfigure.py +++ b/autopsy/reconfigure.py @@ -591,6 +591,7 @@ class ParameterReconfigure(object): _pub_description = None _pub_update = None _service = None + _node = None def __init__(self): """Initialize variables of the object. @@ -611,16 +612,18 @@ def __init__(self): def reconfigure(self, namespace = None, node = None): if node is None: - node = autopsy.node.rospy + self._node = autopsy.node.rospy + else: + self._node = node if namespace is None: - namespace = node.get_name() + namespace = self._node.get_name() - self._pub_description = node.Publisher("%s/parameter_descriptions" % namespace, + self._pub_description = self._node.Publisher("%s/parameter_descriptions" % namespace, ConfigDescription, queue_size = 1, latch = True) - self._pub_update = node.Publisher("%s/parameter_updates" % namespace, + self._pub_update = self._node.Publisher("%s/parameter_updates" % namespace, Config, queue_size = 1, latch = True) - self._service = node.Service("%s/set_parameters" % namespace, Reconfigure, self._reconfigureCallback) + self._service = self._node.Service("%s/set_parameters" % namespace, Reconfigure, self._reconfigureCallback) self._redescribe() self._describePub() From 89c04cbe81abccac27dea20365518aad261d9b5e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jaroslav=20Klap=C3=A1lek?= Date: Wed, 24 Aug 2022 14:27:30 +0200 Subject: [PATCH 6/8] Send parameters and their updates to the ROS Parameter Server --- CHANGELOG.md | 1 + autopsy/reconfigure.py | 11 +++++++++++ 2 files changed, 12 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0740e90..86a0b7d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/). - Parameters can be now used within conditions. - Implementation of `__contains__` to support `if ... in P`. - `update()` now takes optional argument `only_existing` (def. `False`) to only update existing parameters. + - (ROS1 only) Parameters and their values are exposed to the ROS Parameter Server. ## 0.5.1 - 2022-03-23 ### Added diff --git a/autopsy/reconfigure.py b/autopsy/reconfigure.py index 8d7457d..9cbea51 100644 --- a/autopsy/reconfigure.py +++ b/autopsy/reconfigure.py @@ -592,6 +592,7 @@ class ParameterReconfigure(object): _pub_update = None _service = None _node = None + _expose_parameters = False def __init__(self): """Initialize variables of the object. @@ -625,6 +626,12 @@ def reconfigure(self, namespace = None, node = None): Config, queue_size = 1, latch = True) self._service = self._node.Service("%s/set_parameters" % namespace, Reconfigure, self._reconfigureCallback) + # Expose parameters to the ROS Parameter Server (ROS1 only) + if hasattr(self._node, "set_param"): + self._expose_parameters = True + for _name, _param in self._parameters.items(): + self._node.set_param("~%s" % _name, _param.value) + self._redescribe() self._describePub() @@ -757,6 +764,10 @@ def _reconfigureCallback(self, data, response = None): else: self._parameters[param.name].value = self._parameters[param.name].callback(param.value) + # Expose the update to the ROS Parameter Server (ROS1 only) + if self._expose_parameters: + self._node.set_param("~%s" % param.name, param.value) + _updated.append(param.name) From 9638f6dcd17199c11b9ddaac4a5054739deef29c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jaroslav=20Klap=C3=A1lek?= Date: Wed, 24 Aug 2022 15:28:07 +0200 Subject: [PATCH 7/8] Add support for linked variables #3 --- CHANGELOG.md | 1 + README.md | 17 +++++++++++++++++ autopsy/reconfigure.py | 27 ++++++++++++++++++++++++++- 3 files changed, 44 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 86a0b7d..c240e54 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/). - Implementation of `__contains__` to support `if ... in P`. - `update()` now takes optional argument `only_existing` (def. `False`) to only update existing parameters. - (ROS1 only) Parameters and their values are exposed to the ROS Parameter Server. + - `link(ConstrainedP, ConstrainedP)` to link two parameters together. First cannot be larger then the second one. ## 0.5.1 - 2022-03-23 ### Added diff --git a/README.md b/README.md index 416329d..d285807 100644 --- a/README.md +++ b/README.md @@ -185,6 +185,23 @@ P.reconfigure() _Note: Callback function receives new value of the parameter, and is required to return the true new value. It is then filled inside the parameter and announced to the reconfigure GUI._ +##### Linked variables + +Parameters of type `ConstrainedP` can be "linked" together. When done, first parameter cannot exceed the value of the second parameter and vice versa. Linking is performed after defining the parameters using `P.link()`. + +Example: +```python +P = ParameterServer() + +P.range_min = 2.0 +P.range_max = 3.0 + +P.link(P.range_min, P.range_max) + +# P.range_min and P.range_max are now linked. Should min be over max, it is set to max, and vice versa. +``` + + #### Reconfiguration itself ```python diff --git a/autopsy/reconfigure.py b/autopsy/reconfigure.py index 9cbea51..182eaee 100644 --- a/autopsy/reconfigure.py +++ b/autopsy/reconfigure.py @@ -387,6 +387,10 @@ def __operator__(first, second = None, operator = None): class ConstrainedP(Parameter): """Parameter that is constrained by min and max values.""" + # Constrain the value even further by looking at a linked variable. + _link = None + + def __init__(self, name, default, type, min = -2147483647, max = 2147483647, **kwargs): """Initialize a constrained parameter object. @@ -451,6 +455,9 @@ def value(self, new_value): if not self.min <= new_value <= self.max: raise ValueError("Value '%s' is not in %s <= value <= %s range." % (formatNumber(new_value), formatNumber(self.min), formatNumber(self.max))) + if self._link is not None: + new_value = self._link(new_value) + Parameter.value.fset(self, new_value) @@ -732,7 +739,9 @@ def _redescribe(self): def _reupdate(self): """Creates an update description of the parameters.""" - _config = self._get_config(condition = lambda x: x.value != x.default) + # Note: Condition was removed because of the linked variables, as when + # the value should be moved to its default state, nothing would happen. + _config = self._get_config()#condition = lambda x: x.value != x.default) _config.groups = [ GroupState( name = "Default", @@ -891,6 +900,22 @@ def __contains__(self, name): return name in self._parameters + def link(self, param1, param2): + """Links two constrained parameters together so one cannot be more then the other. + + Arguments: + name1 -- name of the first ConstrainedP parameter, str + name2 -- name of the second ConstrainedP parameter, str + """ + + for param in (param1, param2): + if not isinstance(param, ConstrainedP): + raise TypeError("Parameter '%s' is of a type '%s'." % (param.name, type(param))) + + param1._link = lambda x: min(x, param2.value) + param2._link = lambda x: max(x, param1.value) + + def update(self, parameters, only_existing = False): """Updates the parameters according to the passed dictionary. From 04263b1d900b42832b7ea50af5c75a9d7cadaea2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jaroslav=20Klap=C3=A1lek?= Date: Wed, 24 Aug 2022 15:29:44 +0200 Subject: [PATCH 8/8] Release version 0.6.0 --- CHANGELOG.md | 1 + package.xml | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c240e54..977db67 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/). ## Unreleased +## 0.6.0 - 2022-08-24 ### Added - `reconfigure`: - Parameters can be now used within conditions. diff --git a/package.xml b/package.xml index 4f4655d..e874373 100644 --- a/package.xml +++ b/package.xml @@ -4,7 +4,7 @@ schematypens="http://www.w3.org/2001/XMLSchema"?> autopsy - 0.5.1 + 0.6.0 A set of Python utils for F1Tenth project. Jaroslav Klapálek