From 78e9ab3d7db489be8956c50fe91ec7323eec3ea4 Mon Sep 17 00:00:00 2001 From: Luis Camero Date: Tue, 28 Mar 2023 15:16:13 -0400 Subject: [PATCH 01/50] Removed unecessary platform specific decorations --- clearpath_config/platform/decorations.py | 118 ++++++++++++----------- 1 file changed, 61 insertions(+), 57 deletions(-) diff --git a/clearpath_config/platform/decorations.py b/clearpath_config/platform/decorations.py index 844297e..bb8c471 100644 --- a/clearpath_config/platform/decorations.py +++ b/clearpath_config/platform/decorations.py @@ -1,6 +1,3 @@ -from clearpath_config.common import Platform - - # DecorationAccessories class Decorations: # General Decorations @@ -17,8 +14,13 @@ class Bumper: MODELS = [DEFAULT, WIBOTIC] def __init__( - self, enable: bool = True, extension: float = 0.0, model: str = DEFAULT - ) -> None: + self, + name: str, + enable: bool = True, + extension: float = 0.0, + model: str = DEFAULT + ) -> None: + self.name = name self.enabled = True self.extension = 0.0 self.model = self.DEFAULT @@ -29,6 +31,12 @@ def __init__( if model: self.set_model(model) + def get_name(self) -> str: + return self.name + + def set_name(self, name) -> None: + self.name = name + def enable(self) -> None: self.enabled = True @@ -61,55 +69,51 @@ def set_model(self, model: str) -> None: ) self.model = model - # Husky Specific Decorations - class A200: - class TopPlate: - """ - Top Plate on the Husky can be: - - toggled on/off - - swapped for larger plate and pacs plate - - PACS plate is required - """ - - DEFAULT = "default" - LARGE = "large" - PACS = "pacs" - MODELS = [DEFAULT, LARGE, PACS] - - def __init__(self, enable: bool = True, model: str = DEFAULT) -> None: - self.enabled = True - self.extension = 0.0 - self.model = self.DEFAULT - if enable: - self.enable() - if model: - self.set_model(model) - - def enable(self) -> None: - self.enabled = True - - def disable(self) -> None: - self.enabled = False - - def get_model(self) -> str: - return self.model - - def set_model(self, model: str) -> None: - assert ( - model in self.MODELS - ), "Top plate model '%s' is not one of: %s" % (model, self.MODELS) - self.model = model - - -# Base Decorations Config -# - holds the model name for that config -# - to be used by all other configurations. -class BaseDecorationsConfig: - def __init__(self, model) -> None: - assert ( - model in Platform.ALL - ), "Model passed '%s' is not expected. must be one of the following: %s" % ( - model, - Platform.ALL, - ) - self.model = model + class TopPlate: + """ + Top Plate on the Husky can be: + - toggled on/off + - swapped for larger plate and pacs plate + - PACS plate is required + """ + + DEFAULT = "default" + LARGE = "large" + PACS = "pacs" + MODELS = [DEFAULT, LARGE, PACS] + + def __init__( + self, + name: str, + enable: bool = True, + model: str = DEFAULT + ) -> None: + self.name = name + self.enabled = True + self.extension = 0.0 + self.model = self.DEFAULT + if enable: + self.enable() + if model: + self.set_model(model) + + def set_name(self, name: str) -> None: + self.name = name + + def get_name(self) -> str: + return self.name + + def enable(self) -> None: + self.enabled = True + + def disable(self) -> None: + self.enabled = False + + def get_model(self) -> str: + return self.model + + def set_model(self, model: str) -> None: + assert ( + model in self.MODELS + ), "Top plate model '%s' is not one of: %s" % (model, self.MODELS) + self.model = model From f4e0dee3ae6115137fc499c47a3d3e0a89fd75ec Mon Sep 17 00:00:00 2001 From: Luis Camero Date: Tue, 28 Mar 2023 15:16:51 -0400 Subject: [PATCH 02/50] Added base decorations config --- clearpath_config/platform/base.py | 349 ++++++++++++++++++++++++++++++ 1 file changed, 349 insertions(+) create mode 100644 clearpath_config/platform/base.py diff --git a/clearpath_config/platform/base.py b/clearpath_config/platform/base.py new file mode 100644 index 0000000..72a7fa9 --- /dev/null +++ b/clearpath_config/platform/base.py @@ -0,0 +1,349 @@ +from clearpath_config.common import Platform +from clearpath_config.platform.decorations import Decorations +from clearpath_config.platform.pacs import PACS +from copy import deepcopy +from typing import Callable, Generic, List, TypeVar + +# Generic Type +T = TypeVar("T") + + +# Unique Identifier: Name +def uid_name(T) -> str: + return T.get_name() + + +# Unique Identifier: Level +def uid_level(T) -> int: + return T.get_level() + + +# Unique Identifier: Level-Row +def uid_level_row(T) -> tuple: + return (T.get_level(), T.get_row()) + + +# ListConfigs +# - holds a list of an object type +# - generic class +class ListConfig(Generic[T]): + + def __init__(self, uid: Callable) -> None: + self.__list: List[T] = [] + self.__uid: Callable = uid + + def find( + self, + _obj: T, + ) -> T: + for obj in self.__list: + if self.__uid(obj) == self.__uid(_obj): + return obj + return None + + def add( + self, + obj: T, + ) -> None: + assert isinstance(obj, self.__orig_class__.__args__[0]), ( + "Object must be of type %s" % T + ) + assert self.find(obj) is None, ( + "Object with uid %s is not unique." % ( + self.__uid(obj) + ) + ) + self.__list.append(obj) + + def remove( + self, + _obj: T, + ) -> None: + for obj in self.__list: + if self.__uid(obj) == self.__uid(_obj): + self.__list.remove(obj) + return + + def get(self) -> List[T]: + return self.__list + + def set( + self, + _list: List[T], + ) -> None: + # Copy and Clear + tmp_list = deepcopy(self.__list) + self.__list.clear() + # Add One-by-One + try: + for obj in _list: + self.add(obj) + # Restore Save if Failure + except AssertionError: + self.__list = tmp_list + + +# Base Decorations Config +# - holds the model name for that config +# - to be used by all other configurations. +class BaseDecorationsConfig: + + def __init__(self, model) -> None: + assert ( + model in Platform.ALL + ), " ".join( + "Model '%s' is invalid." % ( + model + ), + "Must be one of the following: %s." % ( + Platform.ALL + ) + ) + # Standard Platform Decorations + self.__bumpers = ListConfig[Decorations.Bumper](uid=uid_name) + self.__top_plates = ListConfig[Decorations.TopPlate](uid=uid_name) + # PACS Platform Decorations + self.__full_risers = ListConfig[PACS.FullRiser](uid=uid_level) + self.__row_risers = ListConfig[PACS.RowRiser](uid=uid_level_row) + self.__brackets = ListConfig[PACS.Bracket](uid=uid_name) + + # Bumper: Add + def add_bumper( + self, + # By Object + bumper: Decorations.Bumper = None, + # By Parameters + name: str = None, + enable: bool = True, + extension: float = 0.0, + model: str = Decorations.Bumper.DEFAULT, + ) -> None: + assert bumper or name, "Bumper object or name must be passed" + # Create Object + if name and not bumper: + bumper = Decorations.Bumper( + name=name, + enable=enable, + extension=extension, + model=model + ) + self.__bumpers.add(bumper) + + # Bumper: Remove + def remove_bumper( + self, + # By Object + bumper: Decorations.Bumper = None, + # By Name + name: str = None, + ) -> None: + assert bumper or name, "Bumper object or name must be passed" + # Create Object + if name and not bumper: + bumper = Decorations.Bumper(name) + self.__bumpers.remove(bumper) + + # Bumper: Get + def get_bumpers( + self + ) -> List[Decorations.Bumper]: + return self.__bumpers.get() + + # Bumper: Set + def set_bumpers( + self, + bumpers: List[Decorations.Bumper], + ) -> None: + self.__bumpers.set(bumpers) + + # Top Plate: Add + def add_top_plate( + self, + # By Object + top_plate: Decorations.TopPlate = None, + # By Parameters + name: str = None, + enable: bool = True, + model: str = Decorations.TopPlate.DEFAULT, + ) -> None: + assert top_plate or name, "Top plate object or name must be passed." + if name and not top_plate: + top_plate = Decorations.TopPlate( + name=name, + enable=enable, + model=model + ) + self.__top_plates.add(top_plate) + + # Top Plate: Remove + def remove_top_plate( + self, + # By Object + top_plate: Decorations.TopPlate = None, + # By Name + name: str = None + ) -> None: + assert top_plate or name, "Top plate object or name must be passed." + if name and not top_plate: + top_plate = Decorations.TopPlate(name=name) + self.__top_plates.remove(top_plate) + + # Top Plate: Get + def get_top_plates( + self + ) -> List[Decorations.TopPlate]: + return self.__top_plates.get() + + # Top Plate: Set + def set_top_plates( + self, + top_plates: List[Decorations.TopPlate] + ) -> None: + self.__top_plates.set(top_plates) + + # Full Risers: Add + def add_full_riser( + self, + # By Object + full_riser: PACS.FullRiser = None, + # By Parameters + level: int = None, + height: float = 0.0, + ) -> None: + assert full_riser or level, "Full riser object or level must be passed" + if level and not full_riser: + full_riser = PACS.FullRiser( + level=level, + height=height + ) + self.__full_risers.add(full_riser) + + # Full Risers: Remove + def remove_full_riser( + self, + # By Object + full_riser: PACS.FullRiser = None, + # By Level + level: int = None + ) -> None: + assert full_riser or level, "Full riser object or level must be passed" + if level and not full_riser: + full_riser = PACS.FullRiser(level=level) + self.__full_risers.remove(full_riser) + + # Full Risers: Get + def get_full_risers( + self, + ) -> List[PACS.FullRiser]: + return self.__full_risers.get() + + # Full Risers: Set + def set_full_risers( + self, + full_risers: List[PACS.FullRiser] + ) -> None: + self.__full_risers.set(full_risers) + + # Row Risers: Add + def add_row_riser( + self, + # By Object + row_riser: PACS.RowRiser = None, + # By Parameters + level: int = None, + row: int = None, + height: float = 0.0, + ) -> None: + assert row_riser or (level and row), ( + "Row riser object or level and row must be passed." + ) + if (level and row) and not row_riser: + row_riser = PACS.RowRiser( + level=level, + row=row, + height=height + ) + self.__row_risers.add(row_riser) + + # Row Risers: Remove + def remove_row_riser( + self, + # By Object + row_riser: PACS.RowRiser = None, + # By Level and Row + level: int = None, + row: int = None, + ) -> None: + assert row_riser or (level and row), ( + "Row riser object or level and row must be passed." + ) + if (level and row) and not row_riser: + row_riser = PACS.RowRiser( + level=level, + row=row, + ) + self.__row_risers.remove(row_riser) + + # Row Risers: Get + def get_row_risers( + self, + ) -> List[PACS.RowRiser]: + return self.__row_risers.get() + + # Row Risers: Set + def set_row_risers( + self, + row_risers: List[PACS.RowRiser] + ) -> None: + self.__row_risers.set(row_risers) + + # Brackets: Add + def add_bracket( + self, + # By Object + bracket: PACS.Bracket = None, + # By Parameters + name: str = None, + parent: str = "base_link", + model: str = PACS.Bracket.DEFAULT, + extension: float = 0.0, + xyz: List[float] = [0.0, 0.0, 0.0], + rpy: List[float] = [0.0, 0.0, 0.0] + ) -> None: + assert bracket or name, "Bracket object or name must be passed" + if name and not bracket: + bracket = PACS.Bracket( + name=name, + parent=parent, + model=model, + extension=extension, + xyz=xyz, + rpy=rpy + ) + self.__brackets.add(bracket) + + # Brackets: Remove + def remove_bracket( + self, + # By Object + bracket: PACS.Bracket = None, + # By Parameters + name: str = None + ) -> None: + assert bracket or name, "Bracket object or name must be passed" + if name and not bracket: + bracket = PACS.Bracket(name=name) + self.__brackets.remove(bracket) + + # Brackets: Get + def get_brackets( + self, + ) -> List[PACS.Bracket]: + return self.__brackets.get() + + # Brackets: Set + def set_brackets( + self, + brackets: List[PACS.Bracket], + ) -> None: + self.__brackets.set(brackets) From c0d1346bb04f826eb041dcc35c008504bc7d93dc Mon Sep 17 00:00:00 2001 From: Luis Camero Date: Tue, 28 Mar 2023 15:18:08 -0400 Subject: [PATCH 03/50] Removed PACS specific configs --- clearpath_config/platform/pacs.py | 193 ------------------------------ 1 file changed, 193 deletions(-) diff --git a/clearpath_config/platform/pacs.py b/clearpath_config/platform/pacs.py index c25485b..24b5d19 100644 --- a/clearpath_config/platform/pacs.py +++ b/clearpath_config/platform/pacs.py @@ -165,196 +165,3 @@ def set_extension(self, extension) -> None: raise AssertionError(e.args[0]) assert extension >= 0, "Bracket extension must be a positive value" self.extension = extension - - -# Base PACS Config -# - enable and disable PACS -class BasePACSConfig: - def __init__(self, enabled: bool = True) -> None: - self.enabled = bool(enabled) - - def enable(self) -> None: - self.enabled = True - - def disable(self) -> None: - self.enabled = False - - -# Full Risers Config -# - list of full risers -class FullRisersConfig(BasePACSConfig): - def __init__( - self, enabled: bool = True, full_risers: List[PACS.FullRiser] = [] - ) -> None: - super().__init__(enabled) - self.full_risers = [] - if full_risers: - self.set_full_risers(full_risers) - - def get_full_risers(self) -> list: - return self.full_risers - - def set_full_risers(self, full_risers: List[PACS.FullRiser]) -> None: - temp = deepcopy(self.get_full_risers()) - self.full_risers.clear() - for full_riser in full_risers: - try: - self.add_full_riser(riser=full_riser) - except AssertionError as e: - self.full_risers = temp - raise e - - def add_full_riser( - self, riser: PACS.FullRiser, level: int = None, height: float = None - ) -> None: - assert riser or level, "FullRiser or level of riser must be passed." - levels = [fr.level for fr in self.full_risers] - if riser: - assert isinstance(riser, PACS.FullRiser), "Riser must be of type FullRiser" - assert riser.level not in levels, ( - "Riser of level %s already exists" % riser.level - ) - self.full_risers.append(riser) - else: - assert level not in levels, "Riser of level %s already exists" % level - if height is not None: - self.full_risers.append(PACS.FullRiser(level, height)) - else: - self.full_risers.append(PACS.FullRiser(level)) - - def remove_full_riser(self, riser: PACS.FullRiser, level: int = None) -> bool: - assert riser or level, "FullRiser or level of riser must be passed." - if riser: - level = riser.level - for riser in self.full_risers: - if riser.level == level: - self.full_risers.remove(riser) - return True - return False - - -# Row Risers Config -# - list of row risers -class RowRisersConfig(BasePACSConfig): - def __init__( - self, enabled: bool = True, row_risers: List[PACS.RowRiser] = [] - ) -> None: - super().__init__(enabled) - self.row_risers = [] - if row_risers: - self.set_row_risers(row_risers) - - def get_row_risers(self) -> list: - return self.row_risers - - def set_row_risers(self, row_risers: List[PACS.RowRiser]) -> None: - temp = deepcopy(self.get_row_risers()) - self.row_risers.clear() - for row_riser in row_risers: - try: - self.add_row_riser(riser=row_riser) - except AssertionError as e: - self.full_risers = temp - raise e - - def add_row_riser( - self, riser: PACS.RowRiser, level: int = None, row: int = None - ) -> None: - assert riser or (level and row), "RowRiser or (level and row) must be passed." - levels = [rr.level for rr in self.row_risers] - rows = [rr.row for rr in self.row_risers] - if riser: - assert isinstance(riser, PACS.RowRiser), "Riser must be of type RowRiser" - assert (riser.level not in levels) or ( - riser.row not in rows - ), "Row riser at level %s and row %s already exists" % ( - riser.level, - riser.row, - ) - self.row_risers.append(riser) - else: - assert (level not in levels) or ( - row not in rows - ), "Row riser at level %s and row %s already exists" % (level, row) - - def remove_row_riser( - self, riser: PACS.RowRiser, level: int = None, row: int = None - ) -> None: - assert riser or (level and row), "RowRiser or (level and row) must be passed." - if riser: - level = riser.level - row = riser.row - for riser in self.row_risers: - if riser.level == level: - self.row_risers.remove(riser) - return True - return False - - -# Brackets Config -# - list of brackets -class BracketsConfig: - def __init__(self, brackets: List[PACS.Bracket] = []) -> None: - self.brackets = [] - if brackets: - self.set_brackets(brackets) - - def get_brackets(self) -> list: - return self.brackets - - def set_brackets(self, brackets: List[PACS.Bracket] = []) -> None: - temp = deepcopy(self.get_brackets()) - self.brackets.clear() - for bracket in brackets: - try: - self.add_bracket(bracket) - except AssertionError as e: - self.brackets = temp - raise e - - def add_bracket( - self, - bracket: PACS.Bracket, - name: str = None, - parent: str = None, - model: str = None, - extension: int = 0, - xyz: List[float] = [0.0, 0.0, 0.0], - rpy: List[float] = [0.0, 0.0, 0.0], - ) -> None: - assert bracket or ( - parent and name - ), "Bracket or (parent and name) must be passed." - names = [b.name for b in self.brackets] - if bracket: - assert isinstance( - bracket, PACS.Bracket - ), "Bracket must be of type PACSConfig.Bracket" - assert bracket.name not in names, ( - "Bracket with name: '%s' already exists." % bracket.name - ) - self.brackets.append(bracket) - else: - assert name not in names, "Bracket with name: '%s' already exists." % name - if not model: - model = PACS.Bracket.DEFAULT - self.brackets.append( - PACS.Bracket( - name=name, - parent=parent, - model=model, - extension=extension, - xyz=xyz, - rpy=rpy, - ) - ) - - def remove_bracket(self, bracket: PACS.Bracket, name: str = None) -> bool: - assert bracket or name, "Bracket or name must be passed." - if bracket: - name = bracket.name - for bracket in self.brackets: - if bracket.name == name: - self.brackets.remove(bracket) - return True - return False From d9d1fadb38c800b9ef5c0eb05d74ced923bd0999 Mon Sep 17 00:00:00 2001 From: Luis Camero Date: Tue, 28 Mar 2023 17:04:20 -0400 Subject: [PATCH 04/50] Added get and set functions to ListConfig --- clearpath_config/platform/base.py | 174 +++++++++++++++++++++++++----- 1 file changed, 146 insertions(+), 28 deletions(-) diff --git a/clearpath_config/platform/base.py b/clearpath_config/platform/base.py index 72a7fa9..ac64bb0 100644 --- a/clearpath_config/platform/base.py +++ b/clearpath_config/platform/base.py @@ -2,10 +2,11 @@ from clearpath_config.platform.decorations import Decorations from clearpath_config.platform.pacs import PACS from copy import deepcopy -from typing import Callable, Generic, List, TypeVar +from typing import Any, Callable, Generic, List, TypeVar # Generic Type T = TypeVar("T") +U = TypeVar("U") # Unique Identifier: Name @@ -26,7 +27,7 @@ def uid_level_row(T) -> tuple: # ListConfigs # - holds a list of an object type # - generic class -class ListConfig(Generic[T]): +class ListConfig(Generic[T, U]): def __init__(self, uid: Callable) -> None: self.__list: List[T] = [] @@ -34,11 +35,25 @@ def __init__(self, uid: Callable) -> None: def find( self, - _obj: T, - ) -> T: - for obj in self.__list: - if self.__uid(obj) == self.__uid(_obj): - return obj + _obj: T | U, + ) -> int: + # Object: T: Template + if isinstance(_obj, self.__orig_class__.__args__[0]): + uid = self.__uid(_obj) + # Object: U: Unique ID + elif isinstance(_obj, self.__orig_class__.__args__[1]): + uid = _obj + # Error + else: + raise AssertionError( + "Object must be of type %s or %s" % ( + self.__orig_class__.__args__[0].__name__, + self.__orig_class__.__args__[1].__name__ + ) + ) + for idx, obj in enumerate(self.__list): + if self.__uid(obj) == uid: + return idx return None def add( @@ -55,6 +70,20 @@ def add( ) self.__list.append(obj) + def replace( + self, + obj: T, + ) -> None: + assert isinstance(obj, self.__orig_class__.__args__[0]), ( + "Object must be of type %s" % T + ) + assert self.find(obj) is not None, ( + "Object with uid %s cannot be replaced. Does not exist." % ( + self.__uid(obj) + ) + ) + self.__list[self.find(obj)] = obj + def remove( self, _obj: T, @@ -64,10 +93,26 @@ def remove( self.__list.remove(obj) return - def get(self) -> List[T]: + def get( + self, + _obj: T | U, + ) -> T: + idx = self.find(_obj) + return None if idx is None else self.__list[idx] + + def get_all(self) -> List[T]: return self.__list def set( + self, + obj: T + ) -> None: + if self.find(obj) is None: + self.add(obj) + else: + self.replace(obj) + + def set_all( self, _list: List[T], ) -> None: @@ -100,12 +145,14 @@ def __init__(self, model) -> None: ) ) # Standard Platform Decorations - self.__bumpers = ListConfig[Decorations.Bumper](uid=uid_name) - self.__top_plates = ListConfig[Decorations.TopPlate](uid=uid_name) + self.__bumpers = ListConfig[Decorations.Bumper, str](uid=uid_name) + self.__top_plates = ListConfig[Decorations.TopPlate, str](uid=uid_name) # PACS Platform Decorations - self.__full_risers = ListConfig[PACS.FullRiser](uid=uid_level) - self.__row_risers = ListConfig[PACS.RowRiser](uid=uid_level_row) - self.__brackets = ListConfig[PACS.Bracket](uid=uid_name) + self.__full_risers = ListConfig[PACS.FullRiser, int](uid=uid_level) + self.__row_risers = ListConfig[ + PACS.RowRiser, tuple[int, int]]( + uid=uid_level_row) + self.__brackets = ListConfig[PACS.Bracket, str](uid=uid_name) # Bumper: Add def add_bumper( @@ -144,17 +191,31 @@ def remove_bumper( self.__bumpers.remove(bumper) # Bumper: Get + def get_bumper( + self, + name: str, + ) -> Decorations.Bumper: + return self.__bumpers.get(name) + + # Bumper: Get All def get_bumpers( self ) -> List[Decorations.Bumper]: - return self.__bumpers.get() + return self.__bumpers.get_all() # Bumper: Set + def set_bumper( + self, + bumper: Decorations.Bumper, + ) -> None: + self.__bumpers.set(bumper) + + # Bumper: Set All def set_bumpers( self, bumpers: List[Decorations.Bumper], ) -> None: - self.__bumpers.set(bumpers) + self.__bumpers.set_all(bumpers) # Top Plate: Add def add_top_plate( @@ -189,17 +250,31 @@ def remove_top_plate( self.__top_plates.remove(top_plate) # Top Plate: Get + def get_top_plate( + self, + name: str + ) -> Decorations.TopPlate: + return self.__top_plates.get(name) + + # Top Plate: Get All def get_top_plates( self ) -> List[Decorations.TopPlate]: - return self.__top_plates.get() + return self.__top_plates.get_all() # Top Plate: Set + def set_top_plate( + self, + top_plate: Decorations.TopPlate + ) -> None: + self.__top_plates.set(top_plate) + + # Top Plate: Set All def set_top_plates( self, top_plates: List[Decorations.TopPlate] ) -> None: - self.__top_plates.set(top_plates) + self.__top_plates.set_all(top_plates) # Full Risers: Add def add_full_riser( @@ -231,18 +306,32 @@ def remove_full_riser( full_riser = PACS.FullRiser(level=level) self.__full_risers.remove(full_riser) - # Full Risers: Get + # Full Riser: Get + def get_full_riser( + self, + level: int + ) -> PACS.FullRiser: + return self.__full_risers.get(level) + + # Full Risers: Get All def get_full_risers( self, ) -> List[PACS.FullRiser]: - return self.__full_risers.get() + return self.__full_risers.get_all() - # Full Risers: Set + # Full Riser: Set + def set_full_riser( + self, + full_riser: PACS.FullRiser + ) -> None: + self.__full_risers.set(full_riser) + + # Full Risers: Set All def set_full_risers( self, full_risers: List[PACS.FullRiser] ) -> None: - self.__full_risers.set(full_risers) + self.__full_risers.set_all(full_risers) # Row Risers: Add def add_row_riser( @@ -284,18 +373,33 @@ def remove_row_riser( ) self.__row_risers.remove(row_riser) - # Row Risers: Get + # Row Riser: Get + def get_row_riser( + self, + level: int, + row: int, + ) -> PACS.RowRiser: + return self.__row_risers.get((level, row)) + + # Row Risers: Get All def get_row_risers( self, ) -> List[PACS.RowRiser]: - return self.__row_risers.get() + return self.__row_risers.get_all() # Row Risers: Set + def set_row_riser( + self, + row_riser: PACS.RowRiser, + ) -> None: + self.__row_risers.set(row_riser) + + # Row Risers: Set All def set_row_risers( self, row_risers: List[PACS.RowRiser] ) -> None: - self.__row_risers.set(row_risers) + self.__row_risers.set_all(row_risers) # Brackets: Add def add_bracket( @@ -335,15 +439,29 @@ def remove_bracket( bracket = PACS.Bracket(name=name) self.__brackets.remove(bracket) - # Brackets: Get + # Bracket: Get + def get_bracket( + self, + name: str, + ) -> PACS.Bracket: + return self.__brackets.get(name) + + # Brackets: Get All def get_brackets( self, ) -> List[PACS.Bracket]: - return self.__brackets.get() + return self.__brackets.get_all() + + # Bracket: Set + def set_bracket( + self, + bracket: PACS.Bracket, + ) -> None: + self.__brackets.set(bracket) - # Brackets: Set + # Brackets: Set All def set_brackets( self, brackets: List[PACS.Bracket], ) -> None: - self.__brackets.set(brackets) + self.__brackets.set_all(brackets) From 99698ecd4f3761df90dc191e773d0d527a7ff7f6 Mon Sep 17 00:00:00 2001 From: Luis Camero Date: Tue, 28 Mar 2023 17:04:47 -0400 Subject: [PATCH 05/50] Fixed assertion indenting --- clearpath_config/platform/pacs.py | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/clearpath_config/platform/pacs.py b/clearpath_config/platform/pacs.py index 24b5d19..3c443cf 100644 --- a/clearpath_config/platform/pacs.py +++ b/clearpath_config/platform/pacs.py @@ -1,5 +1,4 @@ from clearpath_config.common import Accessory -from copy import deepcopy from typing import List @@ -147,12 +146,10 @@ def get_model(self) -> str: return self.model def set_model(self, model: str) -> None: - assert ( - model in self.MODELS - ), "Unexpected Bracket model '%s', it must be one of the following: %s" % ( - model, - self.MODELS, - ) + assert model in self.MODELS, " ".join([ + "Unexpected Bracket model '%s'," % model, + "it must be one of the following: %s" % self.MODELS + ]) self.model = model def get_extension(self) -> float: From e5300930c0f616b4aa2aba7898d173b5fdedf77f Mon Sep 17 00:00:00 2001 From: Luis Camero Date: Tue, 28 Mar 2023 17:05:07 -0400 Subject: [PATCH 06/50] Removed unused imports --- clearpath_config/platform/base.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clearpath_config/platform/base.py b/clearpath_config/platform/base.py index ac64bb0..c0bb148 100644 --- a/clearpath_config/platform/base.py +++ b/clearpath_config/platform/base.py @@ -2,7 +2,7 @@ from clearpath_config.platform.decorations import Decorations from clearpath_config.platform.pacs import PACS from copy import deepcopy -from typing import Any, Callable, Generic, List, TypeVar +from typing import Callable, Generic, List, TypeVar # Generic Type T = TypeVar("T") From 5d5b36c1b3284b599915e44c60afbb57ef0f7062 Mon Sep 17 00:00:00 2001 From: Luis Camero Date: Tue, 28 Mar 2023 17:06:35 -0400 Subject: [PATCH 07/50] Fixed assertion indenting --- clearpath_config/platform/decorations.py | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/clearpath_config/platform/decorations.py b/clearpath_config/platform/decorations.py index bb8c471..1ae36c8 100644 --- a/clearpath_config/platform/decorations.py +++ b/clearpath_config/platform/decorations.py @@ -53,9 +53,10 @@ def set_extension(self, extension) -> None: raise AssertionError(e.args[0]) assert isinstance( extension, float - ), "Bumper extension must be of type float, unexpected type '%s'" % type( - extension - ) + ), " ".join([ + "Bumper extension must be of type float,", + " unexpected type '%s'" % type(extension) + ]) assert extension >= 0, "Bumper extension must be a positive value" self.extension = extension @@ -63,9 +64,11 @@ def get_model(self) -> str: return self.model def set_model(self, model: str) -> None: - assert model in self.MODELS, "Bumper model '%s' is not one of: %s" % ( - model, - self.MODELS, + assert model in self.MODELS, ( + "Bumper model '%s' is not one of: %s" % ( + model, + self.MODELS, + ) ) self.model = model From 5902e23578af4f0381fb3fe1518abc51ddc9366c Mon Sep 17 00:00:00 2001 From: Luis Camero Date: Tue, 28 Mar 2023 17:07:03 -0400 Subject: [PATCH 08/50] Updated A200 to iterable decorations --- clearpath_config/platform/a200.py | 39 +++++++++++++++++-------------- 1 file changed, 21 insertions(+), 18 deletions(-) diff --git a/clearpath_config/platform/a200.py b/clearpath_config/platform/a200.py index 4c1eea7..70b6b9d 100644 --- a/clearpath_config/platform/a200.py +++ b/clearpath_config/platform/a200.py @@ -1,28 +1,31 @@ # A200 Husky Platform Configuration from clearpath_config.common import Platform -from clearpath_config.platform.decorations import BaseDecorationsConfig, Decorations -from clearpath_config.platform.pacs import ( - BracketsConfig, - FullRisersConfig, - RowRisersConfig, -) - - -# A200 Husky PACS Configuration -class A200PACSConfig(BracketsConfig, FullRisersConfig, RowRisersConfig): - def __init__(self) -> None: - super().__init__() +from clearpath_config.platform.base import BaseDecorationsConfig +from clearpath_config.platform.decorations import Decorations # A200 Husky Decorations Configuration class A200DecorationsConfig(BaseDecorationsConfig): + def __init__(self) -> None: super().__init__(model=Platform.A200) - self.front_bumper = Decorations.Bumper( - enable=True, extension=0.0, model=Decorations.Bumper.DEFAULT + # Front Bumper + self.add_bumper( + name="front_bumper", + enable=True, + extension=0.0, + model=Decorations.Bumper.DEFAULT + ) + # Rear Bumper + self.add_bumper( + name="rear_bumper", + enable=True, + extension=0.0, + model=Decorations.Bumper.DEFAULT ) - self.rear_bumper = Decorations.Bumper( - enable=True, extension=0.0, model=Decorations.Bumper.DEFAULT + # Top Plate + self.add_top_plate( + name="top_plate", + enable=True, + model=Decorations.TopPlate.DEFAULT ) - self.top_plate = Decorations.A200.TopPlate() - self.pacs = A200PACSConfig() From e54062c9d62ffa573b49a9c50297ce36bc73997d Mon Sep 17 00:00:00 2001 From: Luis Camero Date: Tue, 28 Mar 2023 17:09:04 -0400 Subject: [PATCH 09/50] Updated J100 to iterable decorations --- clearpath_config/platform/j100.py | 27 ++++++++++++++------------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/clearpath_config/platform/j100.py b/clearpath_config/platform/j100.py index 3785045..f92f29a 100644 --- a/clearpath_config/platform/j100.py +++ b/clearpath_config/platform/j100.py @@ -1,23 +1,24 @@ # J100 Jackal Platform Configuration from clearpath_config.common import Platform -from clearpath_config.platform.decorations import BaseDecorationsConfig, Decorations - - -# J100 Jackal PACS Configuration -class J100PACSConfig: - def __init__(self) -> None: - super().__init__() +from clearpath_config.platform.base import BaseDecorationsConfig +from clearpath_config.platform.decorations import Decorations # J100 Jackal Decorations Configuration class J100DecorationsConfig(BaseDecorationsConfig): def __init__(self) -> None: super().__init__(model=Platform.J100) - self.front_bumper = Decorations.Bumper( - enable=True, extension=0.0, model=Decorations.Bumper.DEFAULT + # Front Bumper + self.add_bumper( + name="front_bumper", + enable=True, + extension=0.0, + model=Decorations.Bumper.DEFAULT ) - self.rear_bumper = Decorations.Bumper( - enable=True, extension=0.0, model=Decorations.Bumper.DEFAULT + # Rear Bumper + self.add_bumper( + name="rear_bumper", + enable=True, + extension=0.0, + model=Decorations.Bumper.DEFAULT ) - # No J100 Jackal PACS Config Yet - # self.pacs = J100PACSConfig() From 9e1f35352e53f16d71e6a595988e1838d3d0d777 Mon Sep 17 00:00:00 2001 From: Luis Camero Date: Tue, 28 Mar 2023 17:13:01 -0400 Subject: [PATCH 10/50] Updated path to config in Platform --- clearpath_config/platform/platform.py | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/clearpath_config/platform/platform.py b/clearpath_config/platform/platform.py index ff7652b..24f5474 100644 --- a/clearpath_config/platform/platform.py +++ b/clearpath_config/platform/platform.py @@ -1,5 +1,5 @@ from clearpath_config.common import File, Platform, SerialNumber -from clearpath_config.platform.decorations import BaseDecorationsConfig +from clearpath_config.platform.base import BaseDecorationsConfig from clearpath_config.platform.a200 import A200DecorationsConfig from clearpath_config.platform.j100 import J100DecorationsConfig from clearpath_config.platform.generic import GENERICDecorationsConfig @@ -29,10 +29,10 @@ class DecorationsConfig: def __new__(self, model) -> None: assert ( model in Platform.ALL - ), "Model passed '%s' is not expected. must be one of the following: %s" % ( - model, - Platform.ALL, - ) + ), " ".join([ + "Model passed '%s' is not expected." % model, + "Must be one of the following: %s" % Platform.ALL + ]) return self.MODEL_CONFIGS[model] @@ -72,8 +72,10 @@ def __init__( self.extras = ExtrasConfig() if decorations: assert isinstance(decorations, BaseDecorationsConfig), ( - "Decorations must be of type DecorationsConfig, unexpected type: '%s'" - % type(decorations) + "Decorations must be of type: %s, unexpected type: '%s'" % ( + BaseDecorationsConfig, + type(decorations) + ) ) assert ( decorations.model == self.model @@ -82,8 +84,8 @@ def __init__( if extras: assert isinstance( extras, ExtrasConfig - ), "Extras must be of type ExtrasConfig, unexpected type: '%s'" % type( - extras + ), "Extras must be of type ExtrasConfig, unexpected type: '%s'" % ( + type(extras) ) self.extras = extras From 240487b563b123a36ad88f274ba7d921adeb5618 Mon Sep 17 00:00:00 2001 From: Luis Camero Date: Tue, 28 Mar 2023 17:37:30 -0400 Subject: [PATCH 11/50] Updated path to base decorations config --- clearpath_config/platform/generic.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clearpath_config/platform/generic.py b/clearpath_config/platform/generic.py index 5c21969..381a30e 100644 --- a/clearpath_config/platform/generic.py +++ b/clearpath_config/platform/generic.py @@ -1,6 +1,6 @@ # GENX Generic Robot Platform Configuration from clearpath_config.common import Platform -from clearpath_config.platform.decorations import BaseDecorationsConfig +from clearpath_config.platform.base import BaseDecorationsConfig # GENX Generic Decorations Configuration From f2b598430b5413d2222d348ce8f552f45fcd4294 Mon Sep 17 00:00:00 2001 From: Luis Camero Date: Tue, 28 Mar 2023 17:37:45 -0400 Subject: [PATCH 12/50] Switched parser to new decorations config --- clearpath_config/parser.py | 37 ++++++++++++++++++++----------------- 1 file changed, 20 insertions(+), 17 deletions(-) diff --git a/clearpath_config/parser.py b/clearpath_config/parser.py index da34daa..048aa19 100644 --- a/clearpath_config/parser.py +++ b/clearpath_config/parser.py @@ -1,10 +1,11 @@ from clearpath_config.common import Platform, Accessory from clearpath_config.clearpath_config import ClearpathConfig from clearpath_config.mounts.mounts import MountsConfig, Mount -from clearpath_config.platform.decorations import Decorations, BaseDecorationsConfig +from clearpath_config.platform.base import BaseDecorationsConfig +from clearpath_config.platform.decorations import Decorations from clearpath_config.platform.pacs import PACS from clearpath_config.platform.platform import PlatformConfig -from clearpath_config.platform.a200 import A200DecorationsConfig, A200PACSConfig +from clearpath_config.platform.a200 import A200DecorationsConfig from clearpath_config.platform.j100 import J100DecorationsConfig from clearpath_config.system.system import SystemConfig, HostsConfig, Host from typing import List @@ -55,7 +56,6 @@ def __new__(cls, config: dict) -> HostsConfig: htsconfig.set_onboard(cls.get_hostlists(cls.ONBOARD, hosts)) # Hosts.Remote htsconfig.set_remote(cls.get_hostlists(cls.REMOTE, hosts)) - print(htsconfig.get_remote()) return htsconfig @classmethod @@ -167,11 +167,11 @@ def __new__(cls, config: dict) -> A200DecorationsConfig: pacsconfig.disable() return pacsconfig # PACS.Full_Risers - pacsconfig.full_risers = PACSConfigParser.get_full_risers(pacs) + full_risers = PACSConfigParser.get_full_risers(pacs) # PACS.Row_Risers - pacsconfig.row_risers = PACSConfigParser.get_row_risers(pacs) + row_risers = PACSConfigParser.get_row_risers(pacs) # PACS.Brackets - pacsconfig.brackets = PACSConfigParser.get_brackets(pacs) + brackets = PACSConfigParser.get_brackets(pacs) return pacsconfig """ @@ -194,7 +194,7 @@ class BumperConfigParser(BaseConfigParser): MODEL = "model" def __new__(cls, key: str, config: dict) -> Decorations.Bumper: - bmpconfig = Decorations.Bumper() + bmpconfig = Decorations.Bumper(key) # Bumper bumper = cls.get_optional_val(key, config) if not bumper: @@ -216,8 +216,8 @@ class TopPlateConfigParser(BaseConfigParser): ENABLE = "enable" MODEL = "model" - def __new__(cls, key: str, config: dict) -> Decorations.A200.TopPlate: - topconfig = Decorations.A200.TopPlate() + def __new__(cls, key: str, config: dict) -> Decorations.TopPlate: + topconfig = Decorations.TopPlate(key) # Top_Plate top_plate = cls.get_optional_val(key, config) if not top_plate: @@ -249,13 +249,16 @@ def __new__(cls, config: dict) -> A200DecorationsConfig: # Decorations decorations = dcnparser.get_required_val(dcnparser.DECORATIONS, config) # Decorations.Front_Bumper - dcnconfig.front_bumper = BumperConfigParser(cls.FRONT_BUMPER, decorations) + dcnconfig.set_bumper(BumperConfigParser(cls.FRONT_BUMPER, decorations)) # Decorations.Rear_Bumper - dcnconfig.rear_bumper = BumperConfigParser(cls.REAR_BUMPER, decorations) + dcnconfig.set_bumper(BumperConfigParser(cls.REAR_BUMPER, decorations)) # Decorations.Top_Plate - dcnconfig.top_plate = TopPlateConfigParser(cls.TOP_PLATE, decorations) + dcnconfig.set_top_plate(TopPlateConfigParser(cls.TOP_PLATE, decorations)) # Decorations.PACS - dcnconfig.pacs = PACSConfigParser(Platform.A200, decorations) + pacs = dcnparser.get_required_val(dcnparser.A200.PACS, decorations) + dcnconfig.set_full_risers(PACSConfigParser.get_full_risers(pacs)) + dcnconfig.set_row_risers(PACSConfigParser.get_row_risers(pacs)) + dcnconfig.set_brackets(PACSConfigParser.get_brackets(pacs)) return dcnconfig class J100: @@ -269,9 +272,9 @@ def __new__(cls, config: dict) -> J100DecorationsConfig: # Decorations decorations = dcnparser.get_required_val(dcnparser.DECORATIONS, config) # Decorations.Front_Bumper - dcnconfig.front_bumper = BumperConfigParser(cls.FRONT_BUMPER, decorations) + dcnconfig.set_bumper(BumperConfigParser(cls.FRONT_BUMPER, decorations)) # Decorations.Rear_Bumper - dcnconfig.rear_bumper = BumperConfigParser(cls.REAR_BUMPER, decorations) + dcnconfig.set_bumper(BumperConfigParser(cls.REAR_BUMPER, decorations)) return dcnconfig MODEL_CONFIGS = {Platform.A200: A200, Platform.J100: J100} @@ -341,8 +344,8 @@ def __new__(cls, config: dict) -> Mount.Base: config, ) mounting_link = cls.get_optional_val( - MountParser.Base.MOUNTING_LINK, - config, + MountParser.Base.MOUNTING_LINK, + config, Mount.Base.MOUNTING_LINK, ) return Mount.Base( From 6ccddb5de77daf26405752323a017a271110112f Mon Sep 17 00:00:00 2001 From: Luis Camero Date: Wed, 29 Mar 2023 10:06:29 -0400 Subject: [PATCH 13/50] Removed PACS Config testers --- clearpath_config/tests/test_platform.py | 45 ++++++++++++------------- 1 file changed, 21 insertions(+), 24 deletions(-) diff --git a/clearpath_config/tests/test_platform.py b/clearpath_config/tests/test_platform.py index 48411a9..0431f84 100644 --- a/clearpath_config/tests/test_platform.py +++ b/clearpath_config/tests/test_platform.py @@ -1,10 +1,5 @@ from clearpath_config.platform.decorations import Decorations -from clearpath_config.platform.pacs import ( - PACS, - FullRisersConfig, - RowRisersConfig, - BracketsConfig, -) +from clearpath_config.platform.pacs import PACS from clearpath_config.platform.platform import PlatformConfig, Platform from clearpath_config.tests.test_utils import ( valid_object_variable_check, @@ -68,15 +63,15 @@ def test_serial_number(self): VALID_BUMPER_MODELS = Decorations.Bumper.MODELS # A200 Top Plate Models INVALID_A200_TOP_PLATE_MODELS = ["random", 1] -VALID_A200_TOP_PLATE_MODELS = Decorations.A200.TopPlate.MODELS +VALID_A200_TOP_PLATE_MODELS = Decorations.TopPlate.MODELS # Test Decorations -class TestDecorationsConfig: +class TestDecorations: def test_bumper(self): errors = [] # Enable - bumper = Decorations.Bumper() + bumper = Decorations.Bumper("bumper") try: bumper.enable() except AssertionError as e: @@ -94,7 +89,7 @@ def test_bumper(self): # Extension errors.extend( invalid_object_variable_check( - init_test=lambda extension: Decorations.Bumper(extension=extension), + init_test=lambda extension: Decorations.Bumper("bumper", extension=extension), set_test=lambda obj, extension: Decorations.Bumper.set_extension( obj, extension ), @@ -103,7 +98,7 @@ def test_bumper(self): ) errors.extend( valid_object_variable_check( - init_test=lambda extension: Decorations.Bumper(extension=extension), + init_test=lambda extension: Decorations.Bumper("bumper", extension=extension), set_test=lambda obj, extension: Decorations.Bumper.set_extension( obj, extension ), @@ -114,14 +109,14 @@ def test_bumper(self): # Model errors.extend( invalid_object_variable_check( - init_test=lambda model: Decorations.Bumper(model=model), + init_test=lambda model: Decorations.Bumper("bumper", model=model), set_test=lambda obj, model: Decorations.Bumper.set_model(obj, model), invalid_entries=INVALID_BUMPER_MODELS, ) ) errors.extend( valid_object_variable_check( - init_test=lambda model: Decorations.Bumper(model=model), + init_test=lambda model: Decorations.Bumper("bumper", model=model), set_test=lambda obj, model: Decorations.Bumper.set_model(obj, model), get_func=lambda obj: Decorations.Bumper.get_model(obj), valid_entries=VALID_BUMPER_MODELS, @@ -129,17 +124,17 @@ def test_bumper(self): ) assert_not_errors(errors) - def test_a200_top_plate(self): + def test_top_plate(self): errors = [] # Enable try: - top_plate = Decorations.A200.TopPlate(enable=True) + top_plate = Decorations.TopPlate("top_plate", enable=True) except AssertionError as e: errors.append("Valid intialization failed with error: %s" % e.args[0]) if not top_plate.enabled: errors.append("Top plate initalized enabled not set to enabled.") try: - top_plate = Decorations.A200.TopPlate() + top_plate = Decorations.TopPlate("top_plate") top_plate.enable() except AssertionError as e: errors.append("Enabling top plate failed with error: %s" % e.args[0]) @@ -147,13 +142,13 @@ def test_a200_top_plate(self): errors.append("Top plate enabled not set to enabled.") # Disable try: - top_plate = Decorations.A200.TopPlate(enable=False) + top_plate = Decorations.TopPlate("top_plate", enable=False) except AssertionError as e: errors.append("Valid intialization failed with error: %s" % e.args[0]) if not top_plate.enabled: errors.append("Top plate initalized disabled not set to disabled.") try: - top_plate = Decorations.A200.TopPlate() + top_plate = Decorations.TopPlate("top_plate") top_plate.disable() except AssertionError as e: errors.append("Disabling top plate failed with error: %s" % e.args[0]) @@ -162,8 +157,8 @@ def test_a200_top_plate(self): # Models errors.extend( invalid_object_variable_check( - init_test=lambda model: Decorations.A200.TopPlate(model=model), - set_test=lambda obj, model: Decorations.A200.TopPlate.set_model( + init_test=lambda model: Decorations.TopPlate("top_plate", model=model), + set_test=lambda obj, model: Decorations.TopPlate.set_model( obj, model ), invalid_entries=INVALID_A200_TOP_PLATE_MODELS, @@ -171,11 +166,11 @@ def test_a200_top_plate(self): ) errors.extend( valid_object_variable_check( - init_test=lambda model: Decorations.A200.TopPlate(model=model), - set_test=lambda obj, model: Decorations.A200.TopPlate.set_model( + init_test=lambda model: Decorations.TopPlate("top_plate", model=model), + set_test=lambda obj, model: Decorations.TopPlate.set_model( obj, model ), - get_func=lambda obj: Decorations.A200.TopPlate.get_model(obj), + get_func=lambda obj: Decorations.TopPlate.get_model(obj), valid_entries=VALID_A200_TOP_PLATE_MODELS, ) ) @@ -209,7 +204,7 @@ def test_a200_top_plate(self): VALID_BRACKETS = [[PACS.Bracket("bracket1"), PACS.Bracket("bracket2")]] -class TestPACSConfig: +class TestPACS: def test_full_riser(self): errors = [] # Level @@ -335,6 +330,7 @@ def test_bracket(self): ) assert_not_errors(errors) +""" def test_full_risers_config(self): errors = [] # Full Risers @@ -421,3 +417,4 @@ def test_brackets_config(self): if bktconfig.get_brackets() != VALID_BRACKETS[0][1:]: errors.append("Failed to remove riser") assert_not_errors(errors) +""" From 3f88d692444b00c44c538a08a11ef526fb913ebe Mon Sep 17 00:00:00 2001 From: Luis Camero Date: Thu, 16 Mar 2023 14:12:22 -0400 Subject: [PATCH 14/50] Fixed lint errors in clearpath_config --- clearpath_config/clearpath_config.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/clearpath_config/clearpath_config.py b/clearpath_config/clearpath_config.py index 1edcd3e..95d25dc 100644 --- a/clearpath_config/clearpath_config.py +++ b/clearpath_config/clearpath_config.py @@ -2,6 +2,7 @@ from clearpath_config.platform.platform import PlatformConfig from clearpath_config.mounts.mounts import MountsConfig + # ClearpathConfig: # - top level configurator # - contains @@ -12,4 +13,4 @@ def __init__(self, config: dict = None) -> None: self.system = SystemConfig() self.platform = PlatformConfig() self.mounts = MountsConfig() - #self.sensors = SensorsConfig() + # self.sensors = SensorsConfig() From 3012a170dc9da1c8d9946a36320496e0eca87548 Mon Sep 17 00:00:00 2001 From: Luis Camero Date: Thu, 16 Mar 2023 14:16:29 -0400 Subject: [PATCH 15/50] Fixed lint errors in mounts --- clearpath_config/mounts/mounts.py | 136 ++++++++++++++++++------------ 1 file changed, 81 insertions(+), 55 deletions(-) diff --git a/clearpath_config/mounts/mounts.py b/clearpath_config/mounts/mounts.py index 93fd3f4..cb8bfb6 100644 --- a/clearpath_config/mounts/mounts.py +++ b/clearpath_config/mounts/mounts.py @@ -2,7 +2,8 @@ from copy import deepcopy from math import pi -class Mount(): + +class Mount: FATH_PIVOT = "fath_pivot" FLIR_PTU = "flir_ptu" ALL = [FATH_PIVOT, FLIR_PTU] @@ -11,14 +12,14 @@ class Base(Accessory): MOUNTING_LINK = None def __init__( - self, - name: str, - model: str, - parent: str = Accessory.PARENT, - mounting_link: str = MOUNTING_LINK, - xyz: List[float] = Accessory.XYZ, - rpy: List[float] = Accessory.RPY, - ) -> None: + self, + name: str, + model: str, + parent: str = Accessory.PARENT, + mounting_link: str = MOUNTING_LINK, + xyz: List[float] = Accessory.XYZ, + rpy: List[float] = Accessory.RPY, + ) -> None: super().__init__(name, parent, xyz, rpy) self.model = str() self.set_model(model) @@ -30,8 +31,10 @@ def get_model(self) -> str: return self.model def set_model(self, model: str) -> None: - assert (model in Mount.ALL - ), "Model '%s' must be one of '%s'" % (model, self.MODELS) + assert model in Mount.ALL, "Model '%s' must be one of '%s'" % ( + model, + self.MODELS, + ) self.model = model def get_mounting_link(self) -> str: @@ -41,21 +44,26 @@ def set_mounting_link(self, mounting_link: str) -> None: self.assert_valid_link(mounting_link) self.mounting_link = mounting_link - class FathPivot(Base): MOUNTING_LINK = None ANGLE = 0.0 def __init__( - self, - name: str, - parent: str = Accessory.PARENT, - mounting_link: str = MOUNTING_LINK, - angle: float = ANGLE, - xyz: List[float] = Accessory.XYZ, - rpy: List[float] = Accessory.RPY, - ) -> None: - super().__init__(name, Mount.FATH_PIVOT, parent, mounting_link, xyz, rpy) + self, + name: str, + parent: str = Accessory.PARENT, + mounting_link: str = MOUNTING_LINK, + angle: float = ANGLE, + xyz: List[float] = Accessory.XYZ, + rpy: List[float] = Accessory.RPY, + ) -> None: + super().__init__( + name, + Mount.FATH_PIVOT, + parent, + mounting_link, + xyz, + rpy) self.angle = 0.0 if angle: self.set_angle(angle) @@ -64,11 +72,11 @@ def get_angle(self) -> float: return self.angle def set_angle(self, angle: float) -> None: - assert(-pi < angle <= pi - ), "Angle '%s' must be in radian and between pi and -pi" + assert -pi < angle <= pi, ( + "Angle '%s' must be in radian and between pi and -pi" + ) self.angle = angle - class FlirPTU(Base): # Default Values MOUNTING_LINK = None @@ -84,19 +92,26 @@ class FlirPTU(Base): CONNECTION_TYPES = [TTY, TCP] def __init__( - self, - name: str, - parent: str = Accessory.PARENT, - mounting_link: str = MOUNTING_LINK, - xyz: List[float] = Accessory.XYZ, - rpy: List[float] = Accessory.RPY, - tty_port: str = TTY_PORT, - tcp_port: int = TCP_PORT, - ip: str = IP_ADDRESS, - connection_type: str = CONNECTION_TYPE, - limits_enabled: bool = LIMITS_ENABLED, - ) -> None: - super().__init__(name, Mount.FLIR_PTU, parent, mounting_link, xyz, rpy) + self, + name: str, + parent: str = Accessory.PARENT, + mounting_link: str = MOUNTING_LINK, + xyz: List[float] = Accessory.XYZ, + rpy: List[float] = Accessory.RPY, + tty_port: str = TTY_PORT, + tcp_port: int = TCP_PORT, + ip: str = IP_ADDRESS, + connection_type: str = CONNECTION_TYPE, + limits_enabled: bool = LIMITS_ENABLED, + ) -> None: + super().__init__( + name, + Mount.FLIR_PTU, + parent, + mounting_link, + xyz, + rpy, + ) # Serial Port self.tty_port = File(self.TTY_PORT) self.set_tty_port(tty_port) @@ -123,8 +138,9 @@ def get_tcp_port(self) -> str: return self.tcp_port def set_tcp_port(self, tcp_port: int) -> None: - assert(1024 < tcp_port < 65536 - ), "TCP port '%s' must be in range 1024 to 65536" % tcp_port + assert 1024 < tcp_port < 65536, ( + "TCP port '%s' must be in range 1024 to 65536" % tcp_port + ) self.tcp_port = tcp_port def get_ip(self) -> str: @@ -137,10 +153,12 @@ def get_connection_type(self) -> str: return self.connection_type def set_connection_type(self, connection_type: str) -> None: - assert(connection_type in self.CONNECTION_TYPES - ), "Connection type '%s' must be one of '%s'" % ( - connection_type, self.CONNECTION_TYPES + assert connection_type in self.CONNECTION_TYPES, ( + "Connection type '%s' must be one of '%s'" % ( + connection_type, + self.CONNECTION_TYPES, ) + ) self.connection_type = connection_type def get_limits_enabled(self) -> bool: @@ -152,14 +170,13 @@ def set_limits_enabled(self, limits_enabled: bool) -> None: MODEL = { FATH_PIVOT: FathPivot, FLIR_PTU: FlirPTU, - } + } def __new__(cls, name: str, model: str) -> Base: return Mount.MODEL[model](name) class MountsConfig: - def __init__(self, mounts: List[Mount.Base] = []) -> None: self.mounts = list() self.set_mounts(mounts) @@ -168,25 +185,34 @@ def get_mounts(self) -> List[Mount.Base]: return self.mounts def set_mounts(self, mounts: List[Mount.Base]) -> None: - assert (isinstance(mounts, list) - ), "Mounts must be a list of Mount objects" - assert (all([isinstance(m, Mount.Base) for m in mounts]) - ), "Mounts must be a list of Mount objects" + assert isinstance(mounts, list), ( + "Mounts must be a list of Mount objects" + ) + assert all([isinstance(m, Mount.Base) for m in mounts]), ( + "Mounts must be a list of Mount objects" + ) temp = deepcopy(self.mounts) self.mounts.clear() for mount in mounts: - self.add_mount(mount) + try: + self.add_mount(mount) + except AssertionError as e: + self.mounts = temp + raise e def add_mount(self, mount: Mount.Base) -> None: - assert (isinstance(mount, Mount.Base) - ), "Mount '%s' must be of type Mount." % mount - assert (mount.get_name() not in [m.get_name() for m in self.mounts] - ), "Mount name '%s' must be unique." % mount.get_name() + assert isinstance(mount, Mount.Base), ( + "Mount '%s' must be of type Mount." % mount + ) + assert mount.get_name() not in [m.get_name() for m in self.mounts], ( + "Mount name '%s' must be unique." % mount.get_name() + ) self.mounts.append(mount) def remove_mount(self, mount: Mount.Base = None, name: str = None) -> None: - assert (mount or name - ), "Mount or name of mount must be passed to 'remove_mount'" + assert mount or name, ( + "Mount or name of mount must be passed to 'remove_mount'" + ) if mount: name = mount.get_name() for mount in self.mounts: From 5c6aaa38f391c7cec5eb3234d8ea4d6ffbcd6b5b Mon Sep 17 00:00:00 2001 From: Luis Camero Date: Thu, 30 Mar 2023 16:59:36 -0400 Subject: [PATCH 16/50] Moved ListConfig --- clearpath_config/common.py | 162 ++++++++++++++++++++++++++++++++----- 1 file changed, 141 insertions(+), 21 deletions(-) diff --git a/clearpath_config/common.py b/clearpath_config/common.py index 7f7439b..7ef65e2 100644 --- a/clearpath_config/common.py +++ b/clearpath_config/common.py @@ -1,4 +1,5 @@ -from typing import List +from copy import deepcopy +from typing import Callable, Generic, List, TypeVar import os import re @@ -47,7 +48,8 @@ def is_valid(hostname: str): if len(hostname) > 253: return False # No Trailing Dots - # - not excatly a standard but generally undefined behaviour and should be avoided + # - not exactly a standard, but generally results in undefined + # behaviour and should be avoided if hostname[-1] == ".": return False # Only [A-Z][0-9] and '-' Allowed @@ -71,8 +73,10 @@ def assert_valid(hostname: str): # Only [A-Z][0-9] and '-' Allowed allowed = re.compile(r"(?!-)[A-Z\d-]{1,63}(? bool: @staticmethod def assert_valid(ip: str) -> None: # Must be String - assert isinstance(ip, str), "IP '%s' must be string" % ip + assert isinstance(ip, str), ( + "IP '%s' must be string" % ip) # Must have Four Fields Delimited by '.' fields = ip.split(".") - assert len(fields) == 4, "IP '%s' must have four entries" % ip + assert len(fields) == 4, ( + "IP '%s' must have four entries" % ip) for field in fields: # Fields Must be Integer - assert field.isdecimal(), "IP '%s' entries must be integers" % ip + assert field.isdecimal(), ( + "IP '%s' entries must be integers" % ip) # Fields Must be 8-Bits Wide field_int = int(field) - assert 0 <= field_int < 256, "IP '%s' entries must in range 0 to 255" % ip + assert 0 <= field_int < 256, ( + "IP '%s' entries must in range 0 to 255" % ip) # File @@ -171,6 +179,7 @@ def is_exists(path: str) -> bool: def get_path(self) -> str: return self.path + # SerialNumber # - Clearpath Robots Serial Number # - ex. cpr-j100-0100 @@ -193,7 +202,9 @@ def parse(sn: str) -> tuple: if len(sn) == 3: assert ( sn[0] == "cpr" - ), "Serial Number with three fields (cpr-j100-0001) must start with cpr" + ), "Serial Number with three fields (%s) must start with cpr" % ( + "cpr-j100-0001", + ) sn = sn[1:] # Match to Robot assert sn[0] in Platform.ALL, ( @@ -206,7 +217,8 @@ def parse(sn: str) -> tuple: else: return (sn[0], "xxxx") # Check Number - assert sn[1].isdecimal(), "Serial Number unit entry must be an integer value" + assert sn[1].isdecimal(), ( + "Serial Number unit entry must be an integer value") return (sn[0], sn[1]) def get_model(self) -> str: @@ -234,7 +246,7 @@ def __init__( parent: str = PARENT, xyz: List[float] = XYZ, rpy: List[float] = RPY - ) -> None: + ) -> None: self.name = str() self.parent = str() self.xyz = list() @@ -265,7 +277,9 @@ def set_xyz(self, xyz: List[float]) -> None: assert all( [isinstance(i, float) for i in xyz] ), "XYZ must have all float entries" - assert len(xyz) == 3, "XYZ must be a list of exactly three float values" + assert len(xyz) == 3, ( + "XYZ must be a list of exactly three float values" + ) self.xyz = xyz def get_rpy(self) -> List[float]: @@ -275,19 +289,125 @@ def set_rpy(self, rpy: List[float]) -> None: assert all( [isinstance(i, float) for i in rpy] ), "RPY must have all float entries" - assert len(rpy) == 3, "RPY must be a list of exactly three float values" + assert len(rpy) == 3, ( + "RPY must be a list of exactly three float values") self.rpy = rpy def assert_valid_link(self, link: str) -> None: # Link name must be a string - assert (isinstance(link, str) - ), "Link name '%s' must be string" % link + assert isinstance(link, str), "Link name '%s' must be string" % link # Link name must not be empty - assert (link - ), "Link name '%s' must not be empty" % link + assert link, "Link name '%s' must not be empty" % link # Link name must not have spaces - assert (" " not in link - ), "Link name '%s' must no have spaces" % link + assert " " not in link, "Link name '%s' must no have spaces" % link # Link name must not start with a digit - assert (not link[0].isdigit() - ), "Link name '%s' must not start with a digit" % link + assert not link[0].isdigit(), "Link name '%s' must not start with a digit" % link + + +# ListConfigs +# - holds a list of an object type +# - generic class + +# Generic Type +T = TypeVar("T") +U = TypeVar("U") + + +class ListConfig(Generic[T, U]): + + def __init__(self, uid: Callable) -> None: + self.__list: List[T] = [] + self.__uid: Callable = uid + + def find( + self, + _obj: T | U, + ) -> int: + # Object: T: Template + if isinstance(_obj, self.__orig_class__.__args__[0]): + uid = self.__uid(_obj) + # Object: U: Unique ID + elif isinstance(_obj, self.__orig_class__.__args__[1]): + uid = _obj + # Error + else: + raise AssertionError( + "Object must be of type %s or %s" % ( + self.__orig_class__.__args__[0].__name__, + self.__orig_class__.__args__[1].__name__ + ) + ) + for idx, obj in enumerate(self.__list): + if self.__uid(obj) == uid: + return idx + return None + + def add( + self, + obj: T, + ) -> None: + assert isinstance(obj, self.__orig_class__.__args__[0]), ( + "Object must be of type %s" % T + ) + assert self.find(obj) is None, ( + "Object with uid %s is not unique." % ( + self.__uid(obj) + ) + ) + self.__list.append(obj) + + def replace( + self, + obj: T, + ) -> None: + assert isinstance(obj, self.__orig_class__.__args__[0]), ( + "Object must be of type %s" % T + ) + assert self.find(obj) is not None, ( + "Object with uid %s cannot be replaced. Does not exist." % ( + self.__uid(obj) + ) + ) + self.__list[self.find(obj)] = obj + + def remove( + self, + _obj: T | U, + ) -> None: + idx = self.find(_obj) + if idx is not None: + self.__list.remove(self.__list[idx]) + + def get( + self, + _obj: T | U, + ) -> T: + idx = self.find(_obj) + return None if idx is None else self.__list[idx] + + def get_all(self) -> List[T]: + return self.__list + + def set( + self, + obj: T + ) -> None: + if self.find(obj) is None: + self.add(obj) + else: + self.replace(obj) + + def set_all( + self, + _list: List[T], + ) -> None: + # Copy and Clear + tmp_list = deepcopy(self.__list) + self.__list.clear() + # Add One-by-One + try: + for obj in _list: + self.add(obj) + # Restore Save if Failure + except AssertionError: + self.__list = tmp_list From 5422062f402749574a567d241bc7a73f0045f108 Mon Sep 17 00:00:00 2001 From: Luis Camero Date: Thu, 30 Mar 2023 16:59:55 -0400 Subject: [PATCH 17/50] Updated remove function --- clearpath_config/platform/base.py | 165 +++--------------------------- 1 file changed, 12 insertions(+), 153 deletions(-) diff --git a/clearpath_config/platform/base.py b/clearpath_config/platform/base.py index c0bb148..2cde954 100644 --- a/clearpath_config/platform/base.py +++ b/clearpath_config/platform/base.py @@ -1,12 +1,7 @@ -from clearpath_config.common import Platform +from clearpath_config.common import ListConfig, Platform from clearpath_config.platform.decorations import Decorations from clearpath_config.platform.pacs import PACS -from copy import deepcopy -from typing import Callable, Generic, List, TypeVar - -# Generic Type -T = TypeVar("T") -U = TypeVar("U") +from typing import List # Unique Identifier: Name @@ -24,110 +19,6 @@ def uid_level_row(T) -> tuple: return (T.get_level(), T.get_row()) -# ListConfigs -# - holds a list of an object type -# - generic class -class ListConfig(Generic[T, U]): - - def __init__(self, uid: Callable) -> None: - self.__list: List[T] = [] - self.__uid: Callable = uid - - def find( - self, - _obj: T | U, - ) -> int: - # Object: T: Template - if isinstance(_obj, self.__orig_class__.__args__[0]): - uid = self.__uid(_obj) - # Object: U: Unique ID - elif isinstance(_obj, self.__orig_class__.__args__[1]): - uid = _obj - # Error - else: - raise AssertionError( - "Object must be of type %s or %s" % ( - self.__orig_class__.__args__[0].__name__, - self.__orig_class__.__args__[1].__name__ - ) - ) - for idx, obj in enumerate(self.__list): - if self.__uid(obj) == uid: - return idx - return None - - def add( - self, - obj: T, - ) -> None: - assert isinstance(obj, self.__orig_class__.__args__[0]), ( - "Object must be of type %s" % T - ) - assert self.find(obj) is None, ( - "Object with uid %s is not unique." % ( - self.__uid(obj) - ) - ) - self.__list.append(obj) - - def replace( - self, - obj: T, - ) -> None: - assert isinstance(obj, self.__orig_class__.__args__[0]), ( - "Object must be of type %s" % T - ) - assert self.find(obj) is not None, ( - "Object with uid %s cannot be replaced. Does not exist." % ( - self.__uid(obj) - ) - ) - self.__list[self.find(obj)] = obj - - def remove( - self, - _obj: T, - ) -> None: - for obj in self.__list: - if self.__uid(obj) == self.__uid(_obj): - self.__list.remove(obj) - return - - def get( - self, - _obj: T | U, - ) -> T: - idx = self.find(_obj) - return None if idx is None else self.__list[idx] - - def get_all(self) -> List[T]: - return self.__list - - def set( - self, - obj: T - ) -> None: - if self.find(obj) is None: - self.add(obj) - else: - self.replace(obj) - - def set_all( - self, - _list: List[T], - ) -> None: - # Copy and Clear - tmp_list = deepcopy(self.__list) - self.__list.clear() - # Add One-by-One - try: - for obj in _list: - self.add(obj) - # Restore Save if Failure - except AssertionError: - self.__list = tmp_list - - # Base Decorations Config # - holds the model name for that config # - to be used by all other configurations. @@ -179,15 +70,9 @@ def add_bumper( # Bumper: Remove def remove_bumper( self, - # By Object - bumper: Decorations.Bumper = None, - # By Name - name: str = None, + # By Object or Name + bumper: Decorations.Bumper | str, ) -> None: - assert bumper or name, "Bumper object or name must be passed" - # Create Object - if name and not bumper: - bumper = Decorations.Bumper(name) self.__bumpers.remove(bumper) # Bumper: Get @@ -239,14 +124,9 @@ def add_top_plate( # Top Plate: Remove def remove_top_plate( self, - # By Object - top_plate: Decorations.TopPlate = None, - # By Name - name: str = None + # By Object or Name + top_plate: Decorations.TopPlate | str, ) -> None: - assert top_plate or name, "Top plate object or name must be passed." - if name and not top_plate: - top_plate = Decorations.TopPlate(name=name) self.__top_plates.remove(top_plate) # Top Plate: Get @@ -296,14 +176,9 @@ def add_full_riser( # Full Risers: Remove def remove_full_riser( self, - # By Object - full_riser: PACS.FullRiser = None, - # By Level - level: int = None + # By Object or Level + full_riser: PACS.FullRiser | int, ) -> None: - assert full_riser or level, "Full riser object or level must be passed" - if level and not full_riser: - full_riser = PACS.FullRiser(level=level) self.__full_risers.remove(full_riser) # Full Riser: Get @@ -357,20 +232,9 @@ def add_row_riser( # Row Risers: Remove def remove_row_riser( self, - # By Object - row_riser: PACS.RowRiser = None, - # By Level and Row - level: int = None, - row: int = None, + # By Object or (Level, Row) + row_riser: PACS.RowRiser | tuple[int], ) -> None: - assert row_riser or (level and row), ( - "Row riser object or level and row must be passed." - ) - if (level and row) and not row_riser: - row_riser = PACS.RowRiser( - level=level, - row=row, - ) self.__row_risers.remove(row_riser) # Row Riser: Get @@ -429,14 +293,9 @@ def add_bracket( # Brackets: Remove def remove_bracket( self, - # By Object - bracket: PACS.Bracket = None, - # By Parameters - name: str = None + # By Object or Name + bracket: PACS.Bracket | str, ) -> None: - assert bracket or name, "Bracket object or name must be passed" - if name and not bracket: - bracket = PACS.Bracket(name=name) self.__brackets.remove(bracket) # Bracket: Get From 05ad4ec01acbe71cd69f3b07c57e6c50346eef1d Mon Sep 17 00:00:00 2001 From: Luis Camero Date: Thu, 30 Mar 2023 17:00:45 -0400 Subject: [PATCH 18/50] Removed mount pseudo namespace --- clearpath_config/mounts/mounts.py | 393 +++++++++++++++--------------- clearpath_config/parser.py | 30 +-- 2 files changed, 217 insertions(+), 206 deletions(-) diff --git a/clearpath_config/mounts/mounts.py b/clearpath_config/mounts/mounts.py index cb8bfb6..b1c70e7 100644 --- a/clearpath_config/mounts/mounts.py +++ b/clearpath_config/mounts/mounts.py @@ -1,220 +1,231 @@ -from clearpath_config.common import Accessory, File, IP, List +from clearpath_config.common import ( + Accessory, + File, + IP, + List, + ListConfig +) from copy import deepcopy from math import pi -class Mount: - FATH_PIVOT = "fath_pivot" - FLIR_PTU = "flir_ptu" - ALL = [FATH_PIVOT, FLIR_PTU] - - class Base(Accessory): - MOUNTING_LINK = None - - def __init__( - self, - name: str, - model: str, - parent: str = Accessory.PARENT, - mounting_link: str = MOUNTING_LINK, - xyz: List[float] = Accessory.XYZ, - rpy: List[float] = Accessory.RPY, - ) -> None: - super().__init__(name, parent, xyz, rpy) - self.model = str() - self.set_model(model) - self.mounting_link = "%s_mount" % self.get_name() - if mounting_link: - self.set_mounting_link(mounting_link) - - def get_model(self) -> str: - return self.model - - def set_model(self, model: str) -> None: - assert model in Mount.ALL, "Model '%s' must be one of '%s'" % ( - model, - self.MODELS, - ) - self.model = model +class BaseMount(Accessory): + MOUNTING_LINK = None + + def __init__( + self, + name: str, + model: str, + parent: str = Accessory.PARENT, + mounting_link: str = MOUNTING_LINK, + xyz: List[float] = Accessory.XYZ, + rpy: List[float] = Accessory.RPY, + ) -> None: + super().__init__(name, parent, xyz, rpy) + self.model = str() + self.set_model(model) + self.mounting_link = "%s_mount" % self.get_name() + if mounting_link: + self.set_mounting_link(mounting_link) + + def get_model(self) -> str: + return self.model + + def set_model(self, model: str) -> None: + assert model in Mount.MODEL, "Model '%s' must be one of '%s'" % ( + model, + Mount.MODEL.keys(), + ) + self.model = model + + def get_mounting_link(self) -> str: + return self.mounting_link + + def set_mounting_link(self, mounting_link: str) -> None: + self.assert_valid_link(mounting_link) + self.mounting_link = mounting_link + + +class FathPivot(BaseMount): + MOUNTING_LINK = None + ANGLE = 0.0 + + def __init__( + self, + name: str, + parent: str = Accessory.PARENT, + mounting_link: str = MOUNTING_LINK, + angle: float = ANGLE, + xyz: List[float] = Accessory.XYZ, + rpy: List[float] = Accessory.RPY, + ) -> None: + super().__init__( + name, + Mount.FATH_PIVOT, + parent, + mounting_link, + xyz, + rpy) + self.angle = 0.0 + if angle: + self.set_angle(angle) + + def get_angle(self) -> float: + return self.angle + + def set_angle(self, angle: float) -> None: + assert -pi < angle <= pi, ( + "Angle '%s' must be in radian and between pi and -pi" + ) + self.angle = angle + + +class FlirPTU(BaseMount): + # Default Values + MOUNTING_LINK = None + TTY_PORT = "/dev/ptu" + TCP_PORT = 4000 + IP_ADDRESS = "192.168.131.70" + LIMITS_ENABLED = False + TTY = "tty" + TCP = "tcp" + CONNECTION_TYPE = TTY + # TTY (uses tty_port) + # TCP (uses ip_addr and tcp_port) + CONNECTION_TYPES = [TTY, TCP] + + def __init__( + self, + name: str, + parent: str = Accessory.PARENT, + mounting_link: str = MOUNTING_LINK, + xyz: List[float] = Accessory.XYZ, + rpy: List[float] = Accessory.RPY, + tty_port: str = TTY_PORT, + tcp_port: int = TCP_PORT, + ip: str = IP_ADDRESS, + connection_type: str = CONNECTION_TYPE, + limits_enabled: bool = LIMITS_ENABLED, + ) -> None: + super().__init__( + name, + Mount.FLIR_PTU, + parent, + mounting_link, + xyz, + rpy, + ) + # Serial Port + self.tty_port = File(self.TTY_PORT) + self.set_tty_port(tty_port) + # TCP Port + self.tcp_port = self.TCP_PORT + self.set_tcp_port(tcp_port) + # IP + self.ip = IP() + self.set_ip(ip) + # Connection Type + self.connection_type = self.TTY + self.set_connection_type(connection_type) + # Limits + self.limits_enabled = False + self.set_limits_enabled(limits_enabled) + + def get_tty_port(self) -> str: + return self.tty_port.get_path() + + def set_tty_port(self, tty_port: str) -> None: + self.tty_port = File(tty_port) + + def get_tcp_port(self) -> str: + return self.tcp_port + + def set_tcp_port(self, tcp_port: int) -> None: + assert 1024 < tcp_port < 65536, ( + "TCP port '%s' must be in range 1024 to 65536" % tcp_port + ) + self.tcp_port = tcp_port - def get_mounting_link(self) -> str: - return self.mounting_link + def get_ip(self) -> str: + return str(self.ip) - def set_mounting_link(self, mounting_link: str) -> None: - self.assert_valid_link(mounting_link) - self.mounting_link = mounting_link + def set_ip(self, ip: str) -> None: + self.ip = IP(ip) - class FathPivot(Base): - MOUNTING_LINK = None - ANGLE = 0.0 + def get_connection_type(self) -> str: + return self.connection_type - def __init__( - self, - name: str, - parent: str = Accessory.PARENT, - mounting_link: str = MOUNTING_LINK, - angle: float = ANGLE, - xyz: List[float] = Accessory.XYZ, - rpy: List[float] = Accessory.RPY, - ) -> None: - super().__init__( - name, - Mount.FATH_PIVOT, - parent, - mounting_link, - xyz, - rpy) - self.angle = 0.0 - if angle: - self.set_angle(angle) - - def get_angle(self) -> float: - return self.angle - - def set_angle(self, angle: float) -> None: - assert -pi < angle <= pi, ( - "Angle '%s' must be in radian and between pi and -pi" + def set_connection_type(self, connection_type: str) -> None: + assert connection_type in self.CONNECTION_TYPES, ( + "Connection type '%s' must be one of '%s'" % ( + connection_type, + self.CONNECTION_TYPES, ) - self.angle = angle - - class FlirPTU(Base): - # Default Values - MOUNTING_LINK = None - TTY_PORT = "/dev/ptu" - TCP_PORT = 4000 - IP_ADDRESS = "192.168.131.70" - LIMITS_ENABLED = False - TTY = "tty" - TCP = "tcp" - CONNECTION_TYPE = TTY - # TTY (uses tty_port) - # TCP (uses ip_addr and tcp_port) - CONNECTION_TYPES = [TTY, TCP] - - def __init__( - self, - name: str, - parent: str = Accessory.PARENT, - mounting_link: str = MOUNTING_LINK, - xyz: List[float] = Accessory.XYZ, - rpy: List[float] = Accessory.RPY, - tty_port: str = TTY_PORT, - tcp_port: int = TCP_PORT, - ip: str = IP_ADDRESS, - connection_type: str = CONNECTION_TYPE, - limits_enabled: bool = LIMITS_ENABLED, - ) -> None: - super().__init__( - name, - Mount.FLIR_PTU, - parent, - mounting_link, - xyz, - rpy, - ) - # Serial Port - self.tty_port = File(self.TTY_PORT) - self.set_tty_port(tty_port) - # TCP Port - self.tcp_port = self.TCP_PORT - self.set_tcp_port(tcp_port) - # IP - self.ip = IP() - self.set_ip(ip) - # Connection Type - self.connection_type = self.TTY - self.set_connection_type(connection_type) - # Limits - self.limits_enabled = False - self.set_limits_enabled(limits_enabled) - - def get_tty_port(self) -> str: - return self.tty_port.get_path() - - def set_tty_port(self, tty_port: str) -> None: - self.tty_port = File(tty_port) - - def get_tcp_port(self) -> str: - return self.tcp_port - - def set_tcp_port(self, tcp_port: int) -> None: - assert 1024 < tcp_port < 65536, ( - "TCP port '%s' must be in range 1024 to 65536" % tcp_port - ) - self.tcp_port = tcp_port - - def get_ip(self) -> str: - return str(self.ip) - - def set_ip(self, ip: str) -> None: - self.ip = IP(ip) - - def get_connection_type(self) -> str: - return self.connection_type + ) + self.connection_type = connection_type - def set_connection_type(self, connection_type: str) -> None: - assert connection_type in self.CONNECTION_TYPES, ( - "Connection type '%s' must be one of '%s'" % ( - connection_type, - self.CONNECTION_TYPES, - ) - ) - self.connection_type = connection_type + def get_limits_enabled(self) -> bool: + return self.limits_enabled - def get_limits_enabled(self) -> bool: - return self.limits_enabled + def set_limits_enabled(self, limits_enabled: bool) -> None: + self.limits_enabled = limits_enabled - def set_limits_enabled(self, limits_enabled: bool) -> None: - self.limits_enabled = limits_enabled +class Mount(): + FATH_PIVOT = "fath_pivot" + FLIR_PTU = "flir_ptu" MODEL = { FATH_PIVOT: FathPivot, FLIR_PTU: FlirPTU, } - def __new__(cls, name: str, model: str) -> Base: + def __new__(cls, name: str, model: str) -> BaseMount: + assert model in Mount.MODEL, ( + "Model '%s' must be one of: '%s'" % ( + model, + Mount.MODEL.keys() + ) + ) return Mount.MODEL[model](name) class MountsConfig: - def __init__(self, mounts: List[Mount.Base] = []) -> None: - self.mounts = list() + def __init__(self, mounts: List[BaseMount] = []) -> None: + self.__mounts = ListConfig[BaseMount, str]( + uid=lambda obj: obj.get_name() + ) self.set_mounts(mounts) - def get_mounts(self) -> List[Mount.Base]: - return self.mounts + def get_mounts(self) -> List[BaseMount]: + return self.__mounts.get_all() - def set_mounts(self, mounts: List[Mount.Base]) -> None: - assert isinstance(mounts, list), ( - "Mounts must be a list of Mount objects" - ) - assert all([isinstance(m, Mount.Base) for m in mounts]), ( - "Mounts must be a list of Mount objects" - ) - temp = deepcopy(self.mounts) - self.mounts.clear() - for mount in mounts: - try: - self.add_mount(mount) - except AssertionError as e: - self.mounts = temp - raise e - - def add_mount(self, mount: Mount.Base) -> None: - assert isinstance(mount, Mount.Base), ( - "Mount '%s' must be of type Mount." % mount - ) - assert mount.get_name() not in [m.get_name() for m in self.mounts], ( - "Mount name '%s' must be unique." % mount.get_name() + def set_mounts(self, mounts: List[BaseMount]) -> None: + self.__mounts.set_all(mounts) + + def add_mount( + self, + # By Object + mount: BaseMount = None, + # By Required Parameters + name: str = None, + model: str = None, + ) -> None: + assert mount or (name and model), ( + "Mount object or name and model must be passed." ) - self.mounts.append(mount) + if (name and model) and not mount: + mount = Mount(name, model) + self.__mounts.add(mount) - def remove_mount(self, mount: Mount.Base = None, name: str = None) -> None: + def remove_mount( + self, + mount: BaseMount = None, + name: str = None + ) -> None: assert mount or name, ( - "Mount or name of mount must be passed to 'remove_mount'" + "Mount object or name must be passed." ) - if mount: - name = mount.get_name() - for mount in self.mounts: - if mount.get_name() == name: - self.mounts.remove(mount) + if name and not mount: + self.__mounts.remove(name) + else: + self.__mounts.remove(mount) diff --git a/clearpath_config/parser.py b/clearpath_config/parser.py index 048aa19..851ceed 100644 --- a/clearpath_config/parser.py +++ b/clearpath_config/parser.py @@ -1,6 +1,6 @@ from clearpath_config.common import Platform, Accessory from clearpath_config.clearpath_config import ClearpathConfig -from clearpath_config.mounts.mounts import MountsConfig, Mount +from clearpath_config.mounts.mounts import MountsConfig, Mount, BaseMount, FlirPTU, FathPivot from clearpath_config.platform.base import BaseDecorationsConfig from clearpath_config.platform.decorations import Decorations from clearpath_config.platform.pacs import PACS @@ -337,7 +337,7 @@ class Base(BaseConfigParser): MODEL = "model" MOUNTING_LINK = "mounting_link" - def __new__(cls, config: dict) -> Mount.Base: + def __new__(cls, config: dict) -> BaseMount: a = AccessoryParser(config) model = cls.get_required_val( MountParser.Base.MODEL, @@ -346,9 +346,9 @@ def __new__(cls, config: dict) -> Mount.Base: mounting_link = cls.get_optional_val( MountParser.Base.MOUNTING_LINK, config, - Mount.Base.MOUNTING_LINK, + BaseMount.MOUNTING_LINK, ) - return Mount.Base( + return BaseMount( name=a.get_name(), parent=a.get_parent(), xyz=a.get_xyz(), @@ -361,15 +361,15 @@ class FathPivot(BaseConfigParser): # Keys ANGLE = "angle" - def __new__(cls, config:dict) -> Mount.FathPivot: + def __new__(cls, config:dict) -> FathPivot: b = MountParser.Base(config) # Pivot Angle angle = cls.get_optional_val( MountParser.FathPivot.ANGLE, config, - Mount.FathPivot.ANGLE, + FathPivot.ANGLE, ) - return Mount.FathPivot( + return FathPivot( name=b.get_name(), parent=b.get_parent(), xyz=b.get_xyz(), @@ -386,39 +386,39 @@ class FlirPTU(BaseConfigParser): CONNECTION_TYPE = "connection_type" LIMITS_ENABLED = "limits_enabled" - def __new__(cls, config: dict) -> Mount.Base: + def __new__(cls, config: dict) -> BaseMount: b = MountParser.Base(config) # TTY Port tty_port = cls.get_optional_val( MountParser.FlirPTU.TTY_PORT, config, - Mount.FlirPTU.TTY_PORT, + FlirPTU.TTY_PORT, ) # TCP Port tcp_port = cls.get_optional_val( MountParser.FlirPTU.TCP_PORT, config, - Mount.FlirPTU.TCP_PORT, + FlirPTU.TCP_PORT, ) # IP Address ip = cls.get_optional_val( MountParser.FlirPTU.IP_ADDRESS, config, - Mount.FlirPTU.IP_ADDRESS, + FlirPTU.IP_ADDRESS, ) # Connection Type connection_type = cls.get_optional_val( MountParser.FlirPTU.CONNECTION_TYPE, config, - Mount.FlirPTU.CONNECTION_TYPE + FlirPTU.CONNECTION_TYPE ) # Limits Enabled limits_enabled = cls.get_optional_val( MountParser.FlirPTU.LIMITS_ENABLED, config, - Mount.FlirPTU.LIMITS_ENABLED, + FlirPTU.LIMITS_ENABLED, ) - return Mount.FlirPTU( + return FlirPTU( name=b.get_name(), parent=b.get_parent(), xyz=b.get_xyz(), @@ -435,7 +435,7 @@ def __new__(cls, config: dict) -> Mount.Base: Mount.FATH_PIVOT: FathPivot, Mount.FLIR_PTU: FlirPTU, } - def __new__(cls, config: dict) -> Mount.Base: + def __new__(cls, config: dict) -> BaseMount: model = cls.get_required_val( MountParser.Base.MODEL, config From 75772fad92239de4d530429b932eca8d9d05936a Mon Sep 17 00:00:00 2001 From: Luis Camero Date: Thu, 30 Mar 2023 17:04:36 -0400 Subject: [PATCH 19/50] Small lint fixes in common --- clearpath_config/common.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/clearpath_config/common.py b/clearpath_config/common.py index 7ef65e2..a8b8d62 100644 --- a/clearpath_config/common.py +++ b/clearpath_config/common.py @@ -301,18 +301,19 @@ def assert_valid_link(self, link: str) -> None: # Link name must not have spaces assert " " not in link, "Link name '%s' must no have spaces" % link # Link name must not start with a digit - assert not link[0].isdigit(), "Link name '%s' must not start with a digit" % link - + assert not link[0].isdigit(), ( + "Link name '%s' must not start with a digit" % link + ) -# ListConfigs -# - holds a list of an object type -# - generic class -# Generic Type +# ListConfigs: Generic Types T = TypeVar("T") U = TypeVar("U") +# ListConfigs +# - holds a list of an object type +# - generic class class ListConfig(Generic[T, U]): def __init__(self, uid: Callable) -> None: From 1dda483938dabfeba1b2c05aae6cc763f593e4f1 Mon Sep 17 00:00:00 2001 From: Luis Camero Date: Thu, 30 Mar 2023 17:07:06 -0400 Subject: [PATCH 20/50] Added get and set methods for individual mounts --- clearpath_config/mounts/mounts.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/clearpath_config/mounts/mounts.py b/clearpath_config/mounts/mounts.py index b1c70e7..de9a780 100644 --- a/clearpath_config/mounts/mounts.py +++ b/clearpath_config/mounts/mounts.py @@ -5,7 +5,6 @@ List, ListConfig ) -from copy import deepcopy from math import pi @@ -196,9 +195,21 @@ def __init__(self, mounts: List[BaseMount] = []) -> None: ) self.set_mounts(mounts) + def get_mount( + self, + name: str, + ) -> BaseMount: + return self.__mounts.get(name) + def get_mounts(self) -> List[BaseMount]: return self.__mounts.get_all() + def set_mount( + self, + mount: BaseMount, + ) -> None: + self.__mounts.set(mount) + def set_mounts(self, mounts: List[BaseMount]) -> None: self.__mounts.set_all(mounts) From fdd47dace9ab78df4b88e4bfd7e38f6cac9860b1 Mon Sep 17 00:00:00 2001 From: Luis Camero Date: Thu, 30 Mar 2023 17:20:52 -0400 Subject: [PATCH 21/50] Split up mounts --- clearpath_config/mounts/base.py | 32 +++++ clearpath_config/mounts/fath_pivot.py | 41 ++++++ clearpath_config/mounts/flir_ptu.py | 95 ++++++++++++++ clearpath_config/mounts/mounts.py | 179 +------------------------- 4 files changed, 175 insertions(+), 172 deletions(-) create mode 100644 clearpath_config/mounts/base.py create mode 100644 clearpath_config/mounts/fath_pivot.py create mode 100644 clearpath_config/mounts/flir_ptu.py diff --git a/clearpath_config/mounts/base.py b/clearpath_config/mounts/base.py new file mode 100644 index 0000000..2c591e8 --- /dev/null +++ b/clearpath_config/mounts/base.py @@ -0,0 +1,32 @@ + +from clearpath_config.common import Accessory +from typing import List + + +class BaseMount(Accessory): + MOUNTING_LINK = None + + def __init__( + self, + name: str, + model: str, + parent: str = Accessory.PARENT, + mounting_link: str = MOUNTING_LINK, + xyz: List[float] = Accessory.XYZ, + rpy: List[float] = Accessory.RPY, + ) -> None: + super().__init__(name, parent, xyz, rpy) + self.model = model + self.mounting_link = "%s_mount" % self.get_name() + if mounting_link: + self.set_mounting_link(mounting_link) + + def get_model(self) -> str: + return self.model + + def get_mounting_link(self) -> str: + return self.mounting_link + + def set_mounting_link(self, mounting_link: str) -> None: + self.assert_valid_link(mounting_link) + self.mounting_link = mounting_link diff --git a/clearpath_config/mounts/fath_pivot.py b/clearpath_config/mounts/fath_pivot.py new file mode 100644 index 0000000..7738448 --- /dev/null +++ b/clearpath_config/mounts/fath_pivot.py @@ -0,0 +1,41 @@ + +from clearpath_config.common import Accessory +from clearpath_config.mounts.base import BaseMount +from math import pi +from typing import List + + +class FathPivot(BaseMount): + MODEL = "fath_pivot" + # Default Values + MOUNTING_LINK = None + ANGLE = 0.0 + + def __init__( + self, + name: str, + parent: str = Accessory.PARENT, + mounting_link: str = MOUNTING_LINK, + angle: float = ANGLE, + xyz: List[float] = Accessory.XYZ, + rpy: List[float] = Accessory.RPY, + ) -> None: + super().__init__( + name, + FathPivot.MODEL, + parent, + mounting_link, + xyz, + rpy) + self.angle = 0.0 + if angle: + self.set_angle(angle) + + def get_angle(self) -> float: + return self.angle + + def set_angle(self, angle: float) -> None: + assert -pi < angle <= pi, ( + "Angle '%s' must be in radian and between pi and -pi" + ) + self.angle = angle diff --git a/clearpath_config/mounts/flir_ptu.py b/clearpath_config/mounts/flir_ptu.py new file mode 100644 index 0000000..fea6d78 --- /dev/null +++ b/clearpath_config/mounts/flir_ptu.py @@ -0,0 +1,95 @@ +from clearpath_config.common import Accessory, IP, File +from clearpath_config.mounts.base import BaseMount +from typing import List + + +class FlirPTU(BaseMount): + MODEL = "flir_ptu" + # Default Values + MOUNTING_LINK = None + TTY_PORT = "/dev/ptu" + TCP_PORT = 4000 + IP_ADDRESS = "192.168.131.70" + LIMITS_ENABLED = False + TTY = "tty" + TCP = "tcp" + CONNECTION_TYPE = TTY + # TTY (uses tty_port) + # TCP (uses ip_addr and tcp_port) + CONNECTION_TYPES = [TTY, TCP] + + def __init__( + self, + name: str, + parent: str = Accessory.PARENT, + mounting_link: str = MOUNTING_LINK, + xyz: List[float] = Accessory.XYZ, + rpy: List[float] = Accessory.RPY, + tty_port: str = TTY_PORT, + tcp_port: int = TCP_PORT, + ip: str = IP_ADDRESS, + connection_type: str = CONNECTION_TYPE, + limits_enabled: bool = LIMITS_ENABLED, + ) -> None: + super().__init__( + name, + FlirPTU.MODEL, + parent, + mounting_link, + xyz, + rpy, + ) + # Serial Port + self.tty_port = File(self.TTY_PORT) + self.set_tty_port(tty_port) + # TCP Port + self.tcp_port = self.TCP_PORT + self.set_tcp_port(tcp_port) + # IP + self.ip = IP() + self.set_ip(ip) + # Connection Type + self.connection_type = self.TTY + self.set_connection_type(connection_type) + # Limits + self.limits_enabled = False + self.set_limits_enabled(limits_enabled) + + def get_tty_port(self) -> str: + return self.tty_port.get_path() + + def set_tty_port(self, tty_port: str) -> None: + self.tty_port = File(tty_port) + + def get_tcp_port(self) -> str: + return self.tcp_port + + def set_tcp_port(self, tcp_port: int) -> None: + assert 1024 < tcp_port < 65536, ( + "TCP port '%s' must be in range 1024 to 65536" % tcp_port + ) + self.tcp_port = tcp_port + + def get_ip(self) -> str: + return str(self.ip) + + def set_ip(self, ip: str) -> None: + self.ip = IP(ip) + + def get_connection_type(self) -> str: + return self.connection_type + + def set_connection_type(self, connection_type: str) -> None: + assert connection_type in self.CONNECTION_TYPES, ( + "Connection type '%s' must be one of '%s'" % ( + connection_type, + self.CONNECTION_TYPES, + ) + ) + self.connection_type = connection_type + + def get_limits_enabled(self) -> bool: + return self.limits_enabled + + def set_limits_enabled(self, limits_enabled: bool) -> None: + self.limits_enabled = limits_enabled diff --git a/clearpath_config/mounts/mounts.py b/clearpath_config/mounts/mounts.py index de9a780..54f45e5 100644 --- a/clearpath_config/mounts/mounts.py +++ b/clearpath_config/mounts/mounts.py @@ -1,178 +1,13 @@ -from clearpath_config.common import ( - Accessory, - File, - IP, - List, - ListConfig -) -from math import pi - - -class BaseMount(Accessory): - MOUNTING_LINK = None - - def __init__( - self, - name: str, - model: str, - parent: str = Accessory.PARENT, - mounting_link: str = MOUNTING_LINK, - xyz: List[float] = Accessory.XYZ, - rpy: List[float] = Accessory.RPY, - ) -> None: - super().__init__(name, parent, xyz, rpy) - self.model = str() - self.set_model(model) - self.mounting_link = "%s_mount" % self.get_name() - if mounting_link: - self.set_mounting_link(mounting_link) - - def get_model(self) -> str: - return self.model - - def set_model(self, model: str) -> None: - assert model in Mount.MODEL, "Model '%s' must be one of '%s'" % ( - model, - Mount.MODEL.keys(), - ) - self.model = model - - def get_mounting_link(self) -> str: - return self.mounting_link - - def set_mounting_link(self, mounting_link: str) -> None: - self.assert_valid_link(mounting_link) - self.mounting_link = mounting_link - - -class FathPivot(BaseMount): - MOUNTING_LINK = None - ANGLE = 0.0 - - def __init__( - self, - name: str, - parent: str = Accessory.PARENT, - mounting_link: str = MOUNTING_LINK, - angle: float = ANGLE, - xyz: List[float] = Accessory.XYZ, - rpy: List[float] = Accessory.RPY, - ) -> None: - super().__init__( - name, - Mount.FATH_PIVOT, - parent, - mounting_link, - xyz, - rpy) - self.angle = 0.0 - if angle: - self.set_angle(angle) - - def get_angle(self) -> float: - return self.angle - - def set_angle(self, angle: float) -> None: - assert -pi < angle <= pi, ( - "Angle '%s' must be in radian and between pi and -pi" - ) - self.angle = angle - - -class FlirPTU(BaseMount): - # Default Values - MOUNTING_LINK = None - TTY_PORT = "/dev/ptu" - TCP_PORT = 4000 - IP_ADDRESS = "192.168.131.70" - LIMITS_ENABLED = False - TTY = "tty" - TCP = "tcp" - CONNECTION_TYPE = TTY - # TTY (uses tty_port) - # TCP (uses ip_addr and tcp_port) - CONNECTION_TYPES = [TTY, TCP] - - def __init__( - self, - name: str, - parent: str = Accessory.PARENT, - mounting_link: str = MOUNTING_LINK, - xyz: List[float] = Accessory.XYZ, - rpy: List[float] = Accessory.RPY, - tty_port: str = TTY_PORT, - tcp_port: int = TCP_PORT, - ip: str = IP_ADDRESS, - connection_type: str = CONNECTION_TYPE, - limits_enabled: bool = LIMITS_ENABLED, - ) -> None: - super().__init__( - name, - Mount.FLIR_PTU, - parent, - mounting_link, - xyz, - rpy, - ) - # Serial Port - self.tty_port = File(self.TTY_PORT) - self.set_tty_port(tty_port) - # TCP Port - self.tcp_port = self.TCP_PORT - self.set_tcp_port(tcp_port) - # IP - self.ip = IP() - self.set_ip(ip) - # Connection Type - self.connection_type = self.TTY - self.set_connection_type(connection_type) - # Limits - self.limits_enabled = False - self.set_limits_enabled(limits_enabled) - - def get_tty_port(self) -> str: - return self.tty_port.get_path() - - def set_tty_port(self, tty_port: str) -> None: - self.tty_port = File(tty_port) - - def get_tcp_port(self) -> str: - return self.tcp_port - - def set_tcp_port(self, tcp_port: int) -> None: - assert 1024 < tcp_port < 65536, ( - "TCP port '%s' must be in range 1024 to 65536" % tcp_port - ) - self.tcp_port = tcp_port - - def get_ip(self) -> str: - return str(self.ip) - - def set_ip(self, ip: str) -> None: - self.ip = IP(ip) - - def get_connection_type(self) -> str: - return self.connection_type - - def set_connection_type(self, connection_type: str) -> None: - assert connection_type in self.CONNECTION_TYPES, ( - "Connection type '%s' must be one of '%s'" % ( - connection_type, - self.CONNECTION_TYPES, - ) - ) - self.connection_type = connection_type - - def get_limits_enabled(self) -> bool: - return self.limits_enabled - - def set_limits_enabled(self, limits_enabled: bool) -> None: - self.limits_enabled = limits_enabled +from clearpath_config.common import ListConfig +from clearpath_config.mounts.base import BaseMount +from clearpath_config.mounts.fath_pivot import FathPivot +from clearpath_config.mounts.flir_ptu import FlirPTU +from typing import List class Mount(): - FATH_PIVOT = "fath_pivot" - FLIR_PTU = "flir_ptu" + FATH_PIVOT = FathPivot.MODEL + FLIR_PTU = FlirPTU.MODEL MODEL = { FATH_PIVOT: FathPivot, FLIR_PTU: FlirPTU, From 21b930503b198e7e930a1f9554812714d0cd878d Mon Sep 17 00:00:00 2001 From: Luis Camero Date: Mon, 3 Apr 2023 16:36:43 -0400 Subject: [PATCH 22/50] Added uid checks to ListConfig --- clearpath_config/common.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/clearpath_config/common.py b/clearpath_config/common.py index a8b8d62..0259124 100644 --- a/clearpath_config/common.py +++ b/clearpath_config/common.py @@ -412,3 +412,18 @@ def set_all( # Restore Save if Failure except AssertionError: self.__list = tmp_list + + # Unique Identifier: Name + @staticmethod + def uid_name(T) -> str: + return T.get_name() + + # Unique Identifier: Level + @staticmethod + def uid_level(T) -> int: + return T.get_level() + + # Unique Identifier: Level-Row + @staticmethod + def uid_level_row(T) -> tuple: + return (T.get_level(), T.get_row()) From feb651e186920a9d07094b46cd7814fc6cf06ad3 Mon Sep 17 00:00:00 2001 From: Luis Camero Date: Mon, 3 Apr 2023 16:57:11 -0400 Subject: [PATCH 23/50] Removed mounting link and model --- clearpath_config/mounts/base.py | 18 ++---------------- 1 file changed, 2 insertions(+), 16 deletions(-) diff --git a/clearpath_config/mounts/base.py b/clearpath_config/mounts/base.py index 2c591e8..ab0ecec 100644 --- a/clearpath_config/mounts/base.py +++ b/clearpath_config/mounts/base.py @@ -1,32 +1,18 @@ - from clearpath_config.common import Accessory from typing import List class BaseMount(Accessory): - MOUNTING_LINK = None + MOUNT_MODEL = "base_mount" def __init__( self, name: str, - model: str, parent: str = Accessory.PARENT, - mounting_link: str = MOUNTING_LINK, xyz: List[float] = Accessory.XYZ, rpy: List[float] = Accessory.RPY, ) -> None: super().__init__(name, parent, xyz, rpy) - self.model = model - self.mounting_link = "%s_mount" % self.get_name() - if mounting_link: - self.set_mounting_link(mounting_link) def get_model(self) -> str: - return self.model - - def get_mounting_link(self) -> str: - return self.mounting_link - - def set_mounting_link(self, mounting_link: str) -> None: - self.assert_valid_link(mounting_link) - self.mounting_link = mounting_link + return self.MOUNT_MODEL From d04cab7873b76331e34425855630793bd0c9ae0c Mon Sep 17 00:00:00 2001 From: Luis Camero Date: Mon, 3 Apr 2023 16:58:16 -0400 Subject: [PATCH 24/50] Removed mounting link from fath and flir moutns --- clearpath_config/mounts/fath_pivot.py | 6 +----- clearpath_config/mounts/flir_ptu.py | 6 +----- 2 files changed, 2 insertions(+), 10 deletions(-) diff --git a/clearpath_config/mounts/fath_pivot.py b/clearpath_config/mounts/fath_pivot.py index 7738448..2983b64 100644 --- a/clearpath_config/mounts/fath_pivot.py +++ b/clearpath_config/mounts/fath_pivot.py @@ -6,25 +6,21 @@ class FathPivot(BaseMount): - MODEL = "fath_pivot" + MOUNT_MODEL = "fath_pivot" # Default Values - MOUNTING_LINK = None ANGLE = 0.0 def __init__( self, name: str, parent: str = Accessory.PARENT, - mounting_link: str = MOUNTING_LINK, angle: float = ANGLE, xyz: List[float] = Accessory.XYZ, rpy: List[float] = Accessory.RPY, ) -> None: super().__init__( name, - FathPivot.MODEL, parent, - mounting_link, xyz, rpy) self.angle = 0.0 diff --git a/clearpath_config/mounts/flir_ptu.py b/clearpath_config/mounts/flir_ptu.py index fea6d78..9e2fc72 100644 --- a/clearpath_config/mounts/flir_ptu.py +++ b/clearpath_config/mounts/flir_ptu.py @@ -4,9 +4,8 @@ class FlirPTU(BaseMount): - MODEL = "flir_ptu" + MOUNT_MODEL = "flir_ptu" # Default Values - MOUNTING_LINK = None TTY_PORT = "/dev/ptu" TCP_PORT = 4000 IP_ADDRESS = "192.168.131.70" @@ -22,7 +21,6 @@ def __init__( self, name: str, parent: str = Accessory.PARENT, - mounting_link: str = MOUNTING_LINK, xyz: List[float] = Accessory.XYZ, rpy: List[float] = Accessory.RPY, tty_port: str = TTY_PORT, @@ -33,9 +31,7 @@ def __init__( ) -> None: super().__init__( name, - FlirPTU.MODEL, parent, - mounting_link, xyz, rpy, ) From d6d69ef642404c006af77d0705afbd057663fd5b Mon Sep 17 00:00:00 2001 From: Luis Camero Date: Mon, 3 Apr 2023 19:36:48 -0400 Subject: [PATCH 25/50] Added OrderedListConfig --- clearpath_config/common.py | 120 +++++++++++++++++++++++++++++++++++++ 1 file changed, 120 insertions(+) diff --git a/clearpath_config/common.py b/clearpath_config/common.py index 0259124..8db61a1 100644 --- a/clearpath_config/common.py +++ b/clearpath_config/common.py @@ -427,3 +427,123 @@ def uid_level(T) -> int: @staticmethod def uid_level_row(T) -> tuple: return (T.get_level(), T.get_row()) + + +# OrderedListConfig +# - uid is an integer +# - index will be enforced to match uid +# - obj_to_idx +# - idx_to_obj +class OrderedListConfig(Generic[T]): + + def __init__( + self, + # Unique identifier to index + obj_to_idx: Callable, + idx_to_obj: Callable + ) -> None: + self.__list: List[T] = [] + self.__obj_to_idx: Callable = obj_to_idx + self.__idx_to_obj: Callable = idx_to_obj + + def find( + self, + obj: T | int + ) -> int: + if isinstance(obj, self.__orig_class__.__args__[0]): + idx = self.__obj_to_idx(obj) + elif isinstance(obj, int): + idx = obj + else: + raise AssertionError( + "Object must of type %s or %s" % ( + self.__orig_class__.__args__[0], int + ) + ) + if idx < len(self.__list): + return idx + else: + return None + + def update(self): + for idx, obj in enumerate(self.__list): + if self.__obj_to_idx(obj) != idx: + self.__list[idx] = self.__idx_to_obj(obj, idx) + + def add( + self, + obj: T + ) -> None: + assert isinstance(obj, self.__orig_class__.__args__[0]), ( + "Object must be of type %s" % T + ) + self.__list.append(obj) + self.update() + + def replace( + self, + obj: T, + ) -> None: + idx = self.find(obj) + assert idx is not None, ( + "Object not found. Cannot be replaced" + ) + self.__list[idx] = obj + self.update() + + def remove( + self, + obj: T | int + ) -> None: + idx = self.find(obj) + if idx is not None: + self.__list.remove(self.__list[idx]) + self.update() + + def get( + self, + obj: T | int, + ) -> T: + idx = self.find(obj) + return None if idx is None else self.__list[idx] + + def get_all( + self + ) -> List[T]: + return self.__list + + def set( + self, + obj: T + ) -> None: + if self.find(obj) is None: + self.add(obj) + else: + self.replace(obj) + + def set_all( + self, + _list: List[T], + ) -> None: + # Copy and Clear + tmp_list = deepcopy(self.__list) + self.__list.clear() + # Add One-by-One + try: + for obj in _list: + self.add(obj) + # Restore Save if Failure + except AssertionError: + self.__list = tmp_list + + # Name as Unique ID to Index + @staticmethod + def name_obj_to_idx(obj: T): + name = obj.get_name() + str_idx = name.split("_")[-1] + return int(str_idx) + + @staticmethod + def name_idx_to_obj(obj: T, idx: int): + obj.set_name(obj.get_name_from_idx(idx)) + return obj From 830b7e9bd0b9bb4edfaf8fdb73ef5e2a3c4f210a Mon Sep 17 00:00:00 2001 From: Luis Camero Date: Mon, 3 Apr 2023 19:37:08 -0400 Subject: [PATCH 26/50] Added name from id to BaseMount --- clearpath_config/mounts/base.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/clearpath_config/mounts/base.py b/clearpath_config/mounts/base.py index ab0ecec..be902b8 100644 --- a/clearpath_config/mounts/base.py +++ b/clearpath_config/mounts/base.py @@ -14,5 +14,13 @@ def __init__( ) -> None: super().__init__(name, parent, xyz, rpy) - def get_model(self) -> str: - return self.MOUNT_MODEL + @classmethod + def get_mount_model(cls) -> str: + return cls.MOUNT_MODEL + + @classmethod + def get_name_from_idx(cls, idx: int) -> str: + return "%s_%s" % ( + cls.get_mount_model(), + idx + ) From fd346fe6a3254e4c392f24e981fc5bdd31fdf911 Mon Sep 17 00:00:00 2001 From: Luis Camero Date: Mon, 3 Apr 2023 19:37:54 -0400 Subject: [PATCH 27/50] Removed name as a default parameter --- clearpath_config/mounts/fath_pivot.py | 3 +- clearpath_config/mounts/flir_ptu.py | 3 +- clearpath_config/mounts/pacs.py | 140 ++++++++++++++++++++++++++ 3 files changed, 142 insertions(+), 4 deletions(-) create mode 100644 clearpath_config/mounts/pacs.py diff --git a/clearpath_config/mounts/fath_pivot.py b/clearpath_config/mounts/fath_pivot.py index 2983b64..9e41bc7 100644 --- a/clearpath_config/mounts/fath_pivot.py +++ b/clearpath_config/mounts/fath_pivot.py @@ -12,14 +12,13 @@ class FathPivot(BaseMount): def __init__( self, - name: str, parent: str = Accessory.PARENT, angle: float = ANGLE, xyz: List[float] = Accessory.XYZ, rpy: List[float] = Accessory.RPY, ) -> None: super().__init__( - name, + FathPivot.get_name_from_idx(0), parent, xyz, rpy) diff --git a/clearpath_config/mounts/flir_ptu.py b/clearpath_config/mounts/flir_ptu.py index 9e2fc72..e379a9f 100644 --- a/clearpath_config/mounts/flir_ptu.py +++ b/clearpath_config/mounts/flir_ptu.py @@ -19,7 +19,6 @@ class FlirPTU(BaseMount): def __init__( self, - name: str, parent: str = Accessory.PARENT, xyz: List[float] = Accessory.XYZ, rpy: List[float] = Accessory.RPY, @@ -30,7 +29,7 @@ def __init__( limits_enabled: bool = LIMITS_ENABLED, ) -> None: super().__init__( - name, + FlirPTU.get_name_from_idx(0), parent, xyz, rpy, diff --git a/clearpath_config/mounts/pacs.py b/clearpath_config/mounts/pacs.py new file mode 100644 index 0000000..8204951 --- /dev/null +++ b/clearpath_config/mounts/pacs.py @@ -0,0 +1,140 @@ +from clearpath_config.common import Accessory +from clearpath_config.mounts.base import BaseMount +from typing import List + + +# PACS +# - all PACS structures +class PACS: + MAX_ROWS = 8 + MAX_COLUMNS = 7 + + class Riser(BaseMount): + MOUNT_MODEL = "pacs_riser" + THICKNESS = 0.00635 + + def __init__( + self, + rows: int, + columns: int, + height: float, + thickness: float = THICKNESS, + parent: str = Accessory.PARENT, + xyz: List[float] = Accessory.XYZ, + rpy: List[float] = Accessory.RPY, + ) -> None: + super().__init__( + name=PACS.Riser.get_name_from_idx(0), + parent=parent, + xyz=xyz, + rpy=rpy + ) + self.rows: int = 0 + self.set_rows(rows) + self.columns: int = 0 + self.set_columns(columns) + self.height: float = 0.0 + self.set_height(height) + self.thickness: float = 0.0 + self.set_thickness(thickness) + + def get_rows(self) -> int: + return self.rows + + def set_rows(self, rows: int) -> None: + assert isinstance(rows, int), ( + "Riser rows must be an integer." + ) + assert 0 < rows <= PACS.MAX_ROWS, ( + "Riser rows must be between %s and %s" % ( + 0, PACS.MAX_ROWS + ) + ) + self.rows = rows + + def get_columns(self) -> int: + return self.columns + + def set_columns(self, columns: int): + assert isinstance(columns, int), ( + "Riser columns must be an integer." + ) + assert 0 < columns <= PACS.MAX_ROWS, ( + "Riser rows must be between %s and %s" % ( + 0, PACS.MAX_ROWS + ) + ) + self.columns = columns + + def get_height(self) -> float: + return self.height + + def set_height(self, height: float) -> None: + assert height >= 0, "Height must be at least 0" + self.height = height + + def get_thickness(self) -> None: + return self.thickness + + def set_thickness(self, thickness: float) -> None: + assert thickness > 0, "Thickness must be greater than 0" + self.thickness = thickness + + class Bracket(BaseMount): + """ + Bracket: + - small apapter plate + - allows multiple sensors to be mounted + - should be added to any + """ + MOUNT_MODEL = "pacs_bracket" + HORIZONTAL = "horizontal" + HORIZONTAL_LARGE = "large" + VERTICAL = "vertical" + DEFAULT = HORIZONTAL + MODELS = [HORIZONTAL, HORIZONTAL_LARGE, VERTICAL] + + def __init__( + self, + parent: str = "base_link", + model: str = DEFAULT, + extension: float = 0.0, + xyz: List[float] = [0.0, 0.0, 0.0], + rpy: List[float] = [0.0, 0.0, 0.0], + ) -> None: + # Initialize Accessory + super().__init__( + name=PACS.Bracket.get_name_from_idx(0), + parent=parent, + xyz=xyz, + rpy=rpy + ) + # Model: type of bracket + self.model = PACS.Bracket.DEFAULT + if model: + self.set_model(model) + # Extension: length of standoffs in meters + self.extension = 0.0 + if extension: + self.set_extension(extension) + + def get_model(self) -> str: + return self.model + + def set_model(self, model: str) -> None: + assert model in self.MODELS, " ".join([ + "Unexpected Bracket model '%s'," % model, + "it must be one of the following: %s" % self.MODELS + ]) + self.model = model + + def get_extension(self) -> float: + return self.extension + + def set_extension(self, extension) -> None: + try: + extension = float(extension) + except ValueError as e: + raise AssertionError(e.args[0]) + assert extension >= 0, "Bracket extension must be a positive value" + self.extension = extension From 909893622adbdc21ed4c3fb3e472d802ab2d8c1c Mon Sep 17 00:00:00 2001 From: Luis Camero Date: Mon, 3 Apr 2023 19:38:20 -0400 Subject: [PATCH 28/50] Removed PACS from platform --- clearpath_config/platform/pacs.py | 164 ------------------------------ 1 file changed, 164 deletions(-) delete mode 100644 clearpath_config/platform/pacs.py diff --git a/clearpath_config/platform/pacs.py b/clearpath_config/platform/pacs.py deleted file mode 100644 index 3c443cf..0000000 --- a/clearpath_config/platform/pacs.py +++ /dev/null @@ -1,164 +0,0 @@ -from clearpath_config.common import Accessory -from typing import List - - -# PACS -# - all PACS structures -class PACS: - # PACS Components - class FullRiser: - """ - Full Riser: - - spans the entire platorm - - acts as another plate - - creates a grid of mounting locations - - can be added at levels that correspond to 10 cm increments - """ - - def __init__(self, level: int, height: float = None) -> None: - # Level of riser (i.e. first floor, second floor, etc.) - self.level = int() - self.set_level(level) - # Height in meters from top plate to top of riser. - self.height = float() - if height is not None: - self.set_height(height) - else: - self.set_height(0.1 * self.level) - - def get_level(self) -> int: - return self.level - - def set_level(self, level: int) -> None: - if isinstance(level, float): - assert level.is_integer(), "Riser level must be an integer" - try: - level = int(level) - except ValueError as e: - raise AssertionError(e.args[0]) - assert level > 0, "Riser level must be greater than 0" - self.level = level - - def get_height(self) -> float: - return self.height - - def set_height(self, height: float) -> None: - try: - height = float(height) - except ValueError as e: - raise AssertionError(e.args[0]) - assert height >= 0, "Height must be greater than or equal to 0.0" - self.height = height - - class RowRiser: - """ - Row Riser: - - spans one row of the full riser - - can be added at levels that correspond to 10 cm increments - """ - - def __init__(self, level: int, row: int, height: float = None) -> None: - # Level of riser (i.e. first floor, second floor, etc.) - self.level = int() - self.set_level(level) - # Row of row riser (i.e. frist row, ..., last row: robot specifc) - self.row = int() - self.set_row(row) - # Height in meters from top plate to top of riser. - self.height = float() - if height is not None: - self.set_height(height) - else: - self.set_height(0.1 * self.level) - - def get_level(self) -> int: - return self.level - - def set_level(self, level: int) -> None: - if isinstance(level, float): - assert level.is_integer(), "Riser level must be an integer" - try: - level = int(level) - except ValueError as e: - raise AssertionError(e.args[0]) - assert level > 0, "Riser level must be greater than 0" - self.level = level - - def get_row(self) -> int: - return self.row - - def set_row(self, row: int) -> None: - if isinstance(row, float): - assert row.is_integer(), "Riser row must be an integer" - try: - row = int(row) - except ValueError as e: - raise AssertionError(e.args[0]) - assert row > 0, "Riser row must be greater than 0" - self.row = row - - def get_height(self) -> float: - return self.height - - def set_height(self, height: float) -> None: - try: - height = float(height) - except ValueError as e: - raise AssertionError(e.args[0]) - assert height >= 0, "Height must be greater than or equal to 0.0" - self.height = height - - class Bracket(Accessory): - """ - Bracket: - - small apapter plate - - allows multiple sensors to be mounted - - should be added to any - """ - - HORIZONTAL = "horizontal" - HORIZONTAL_LARGE = "large" - VERTICAL = "vertical" - DEFAULT = HORIZONTAL - MODELS = [HORIZONTAL, HORIZONTAL_LARGE, VERTICAL] - - def __init__( - self, - name: str, - parent: str = "base_link", - model: str = DEFAULT, - extension: float = 0.0, - xyz: List[float] = [0.0, 0.0, 0.0], - rpy: List[float] = [0.0, 0.0, 0.0], - ) -> None: - # Initialize Accessory - super().__init__(parent=parent, name=name, xyz=xyz, rpy=rpy) - # Model: type of bracket - self.model = PACS.Bracket.DEFAULT - if model: - self.set_model(model) - # Extension: length of standoffs in meters - self.extension = 0.0 - if extension: - self.set_extension(extension) - - def get_model(self) -> str: - return self.model - - def set_model(self, model: str) -> None: - assert model in self.MODELS, " ".join([ - "Unexpected Bracket model '%s'," % model, - "it must be one of the following: %s" % self.MODELS - ]) - self.model = model - - def get_extension(self) -> float: - return self.extension - - def set_extension(self, extension) -> None: - try: - extension = float(extension) - except ValueError as e: - raise AssertionError(e.args[0]) - assert extension >= 0, "Bracket extension must be a positive value" - self.extension = extension From 7e07db60615a3efae0c121a7c16cad9238c9f825 Mon Sep 17 00:00:00 2001 From: Luis Camero Date: Mon, 3 Apr 2023 19:40:07 -0400 Subject: [PATCH 29/50] Moved ListConfig and all PACS from the Platform base --- clearpath_config/platform/base.py | 199 +----------------------------- 1 file changed, 6 insertions(+), 193 deletions(-) diff --git a/clearpath_config/platform/base.py b/clearpath_config/platform/base.py index 2cde954..ab965dd 100644 --- a/clearpath_config/platform/base.py +++ b/clearpath_config/platform/base.py @@ -1,24 +1,8 @@ from clearpath_config.common import ListConfig, Platform from clearpath_config.platform.decorations import Decorations -from clearpath_config.platform.pacs import PACS from typing import List -# Unique Identifier: Name -def uid_name(T) -> str: - return T.get_name() - - -# Unique Identifier: Level -def uid_level(T) -> int: - return T.get_level() - - -# Unique Identifier: Level-Row -def uid_level_row(T) -> tuple: - return (T.get_level(), T.get_row()) - - # Base Decorations Config # - holds the model name for that config # - to be used by all other configurations. @@ -36,14 +20,12 @@ def __init__(self, model) -> None: ) ) # Standard Platform Decorations - self.__bumpers = ListConfig[Decorations.Bumper, str](uid=uid_name) - self.__top_plates = ListConfig[Decorations.TopPlate, str](uid=uid_name) - # PACS Platform Decorations - self.__full_risers = ListConfig[PACS.FullRiser, int](uid=uid_level) - self.__row_risers = ListConfig[ - PACS.RowRiser, tuple[int, int]]( - uid=uid_level_row) - self.__brackets = ListConfig[PACS.Bracket, str](uid=uid_name) + self.__bumpers = ListConfig[ + Decorations.Bumper, str]( + uid=ListConfig.uid_name) + self.__top_plates = ListConfig[ + Decorations.TopPlate, str]( + uid=ListConfig.uid_name) # Bumper: Add def add_bumper( @@ -155,172 +137,3 @@ def set_top_plates( top_plates: List[Decorations.TopPlate] ) -> None: self.__top_plates.set_all(top_plates) - - # Full Risers: Add - def add_full_riser( - self, - # By Object - full_riser: PACS.FullRiser = None, - # By Parameters - level: int = None, - height: float = 0.0, - ) -> None: - assert full_riser or level, "Full riser object or level must be passed" - if level and not full_riser: - full_riser = PACS.FullRiser( - level=level, - height=height - ) - self.__full_risers.add(full_riser) - - # Full Risers: Remove - def remove_full_riser( - self, - # By Object or Level - full_riser: PACS.FullRiser | int, - ) -> None: - self.__full_risers.remove(full_riser) - - # Full Riser: Get - def get_full_riser( - self, - level: int - ) -> PACS.FullRiser: - return self.__full_risers.get(level) - - # Full Risers: Get All - def get_full_risers( - self, - ) -> List[PACS.FullRiser]: - return self.__full_risers.get_all() - - # Full Riser: Set - def set_full_riser( - self, - full_riser: PACS.FullRiser - ) -> None: - self.__full_risers.set(full_riser) - - # Full Risers: Set All - def set_full_risers( - self, - full_risers: List[PACS.FullRiser] - ) -> None: - self.__full_risers.set_all(full_risers) - - # Row Risers: Add - def add_row_riser( - self, - # By Object - row_riser: PACS.RowRiser = None, - # By Parameters - level: int = None, - row: int = None, - height: float = 0.0, - ) -> None: - assert row_riser or (level and row), ( - "Row riser object or level and row must be passed." - ) - if (level and row) and not row_riser: - row_riser = PACS.RowRiser( - level=level, - row=row, - height=height - ) - self.__row_risers.add(row_riser) - - # Row Risers: Remove - def remove_row_riser( - self, - # By Object or (Level, Row) - row_riser: PACS.RowRiser | tuple[int], - ) -> None: - self.__row_risers.remove(row_riser) - - # Row Riser: Get - def get_row_riser( - self, - level: int, - row: int, - ) -> PACS.RowRiser: - return self.__row_risers.get((level, row)) - - # Row Risers: Get All - def get_row_risers( - self, - ) -> List[PACS.RowRiser]: - return self.__row_risers.get_all() - - # Row Risers: Set - def set_row_riser( - self, - row_riser: PACS.RowRiser, - ) -> None: - self.__row_risers.set(row_riser) - - # Row Risers: Set All - def set_row_risers( - self, - row_risers: List[PACS.RowRiser] - ) -> None: - self.__row_risers.set_all(row_risers) - - # Brackets: Add - def add_bracket( - self, - # By Object - bracket: PACS.Bracket = None, - # By Parameters - name: str = None, - parent: str = "base_link", - model: str = PACS.Bracket.DEFAULT, - extension: float = 0.0, - xyz: List[float] = [0.0, 0.0, 0.0], - rpy: List[float] = [0.0, 0.0, 0.0] - ) -> None: - assert bracket or name, "Bracket object or name must be passed" - if name and not bracket: - bracket = PACS.Bracket( - name=name, - parent=parent, - model=model, - extension=extension, - xyz=xyz, - rpy=rpy - ) - self.__brackets.add(bracket) - - # Brackets: Remove - def remove_bracket( - self, - # By Object or Name - bracket: PACS.Bracket | str, - ) -> None: - self.__brackets.remove(bracket) - - # Bracket: Get - def get_bracket( - self, - name: str, - ) -> PACS.Bracket: - return self.__brackets.get(name) - - # Brackets: Get All - def get_brackets( - self, - ) -> List[PACS.Bracket]: - return self.__brackets.get_all() - - # Bracket: Set - def set_bracket( - self, - bracket: PACS.Bracket, - ) -> None: - self.__brackets.set(bracket) - - # Brackets: Set All - def set_brackets( - self, - brackets: List[PACS.Bracket], - ) -> None: - self.__brackets.set_all(brackets) From 580e2f16de68d2ce6aefcf0c3b8e56a8d684bb3f Mon Sep 17 00:00:00 2001 From: Luis Camero Date: Mon, 3 Apr 2023 19:52:19 -0400 Subject: [PATCH 30/50] Added mounts as individual ordered lists --- clearpath_config/mounts/mounts.py | 302 ++++++++++++++++++++++++++---- 1 file changed, 264 insertions(+), 38 deletions(-) diff --git a/clearpath_config/mounts/mounts.py b/clearpath_config/mounts/mounts.py index 54f45e5..f0f4839 100644 --- a/clearpath_config/mounts/mounts.py +++ b/clearpath_config/mounts/mounts.py @@ -1,16 +1,22 @@ -from clearpath_config.common import ListConfig +from clearpath_config.common import Accessory, OrderedListConfig from clearpath_config.mounts.base import BaseMount from clearpath_config.mounts.fath_pivot import FathPivot from clearpath_config.mounts.flir_ptu import FlirPTU +from clearpath_config.mounts.pacs import PACS from typing import List class Mount(): - FATH_PIVOT = FathPivot.MODEL - FLIR_PTU = FlirPTU.MODEL + FATH_PIVOT = FathPivot.MOUNT_MODEL + FLIR_PTU = FlirPTU.MOUNT_MODEL + PACS_RISER = PACS.Riser.MOUNT_MODEL + PACS_BRACKET = PACS.Bracket.MOUNT_MODEL + MODEL = { FATH_PIVOT: FathPivot, FLIR_PTU: FlirPTU, + PACS_RISER: PACS.Riser, + PACS_BRACKET: PACS.Bracket } def __new__(cls, name: str, model: str) -> BaseMount: @@ -24,54 +30,274 @@ def __new__(cls, name: str, model: str) -> BaseMount: class MountsConfig: - def __init__(self, mounts: List[BaseMount] = []) -> None: - self.__mounts = ListConfig[BaseMount, str]( - uid=lambda obj: obj.get_name() + def __init__(self) -> None: + # Fath Pivot + self.__fath_pivots = ( + OrderedListConfig[FathPivot]( + OrderedListConfig.name_obj_to_idx, + OrderedListConfig.name_idx_to_obj + ) + ) + # Flir PTU + self.__flir_ptus = ( + OrderedListConfig[FlirPTU]( + OrderedListConfig.name_obj_to_idx, + OrderedListConfig.name_idx_to_obj + ) + ) + # PACS Riser + self.__pacs_risers = ( + OrderedListConfig[PACS.Riser]( + OrderedListConfig.name_obj_to_idx, + OrderedListConfig.name_idx_to_obj + ) + ) + # PACS Brackets + self.__pacs_brackets = ( + OrderedListConfig[PACS.Bracket]( + OrderedListConfig.name_obj_to_idx, + OrderedListConfig.name_idx_to_obj + ) ) - self.set_mounts(mounts) - def get_mount( + # FathPivot: Add + def add_fath_pivot( self, - name: str, - ) -> BaseMount: - return self.__mounts.get(name) + # By Object + fath_pivot: FathPivot = None, + # By Parameters + parent: str = "base_link", + angle: float = FathPivot.ANGLE, + xyz: List[float] = Accessory.XYZ, + rpy: List[float] = Accessory.RPY + ): + if not fath_pivot: + fath_pivot = FathPivot( + parent, + angle, + xyz, + rpy + ) + self.__fath_pivots.add(fath_pivot) + + # FathPivot: Remove + def remove_fath_pivot( + self, + # By Object or Index + fath_pivot: FathPivot | int, + ) -> None: + self.__fath_pivots.remove(fath_pivot) + + # FathPivot: Get + def get_fath_pivot( + self, + idx: int + ) -> FathPivot: + self.__fath_pivots.get(idx) - def get_mounts(self) -> List[BaseMount]: - return self.__mounts.get_all() + # FathPivot: Get All + def get_fath_pivots( + self, + ) -> List[FathPivot]: + return self.__fath_pivots.get_all() - def set_mount( + # FathPivot: Set + def set_fath_pivot( self, - mount: BaseMount, + fath_pivot: FathPivot ) -> None: - self.__mounts.set(mount) + self.__fath_pivots.set(fath_pivot) - def set_mounts(self, mounts: List[BaseMount]) -> None: - self.__mounts.set_all(mounts) + # FathPivot: Set All + def set_fath_pivots( + self, + fath_pivots: List[FathPivot], + ) -> None: + self.__fath_pivots.set_all(fath_pivots) - def add_mount( + # FlirPTU: Add + def add_flir_ptu( self, # By Object - mount: BaseMount = None, - # By Required Parameters - name: str = None, - model: str = None, + flir_ptu: FlirPTU = None, + # By Parameters + parent: str = Accessory.PARENT, + xyz: List[float] = Accessory.XYZ, + rpy: List[float] = Accessory.RPY, + tty_port: str = FlirPTU.TTY_PORT, + tcp_port: int = FlirPTU.TCP_PORT, + ip: str = FlirPTU.IP_ADDRESS, + connection_type: str = FlirPTU.CONNECTION_TYPE, + limits_enabled: bool = FlirPTU.LIMITS_ENABLED, ) -> None: - assert mount or (name and model), ( - "Mount object or name and model must be passed." - ) - if (name and model) and not mount: - mount = Mount(name, model) - self.__mounts.add(mount) + if not flir_ptu: + flir_ptu = FlirPTU( + parent, + xyz, + rpy, + tty_port, + tcp_port, + ip, + connection_type, + limits_enabled, + ) + self.__flir_ptus.add(flir_ptu) + + # FlirPTU: Remove + def remove_flir_ptu( + self, + # By Object or Index + flir_ptu: FlirPTU | int, + ) -> None: + self.__flir_ptus.remove(flir_ptu) + + # FlirPTU: Get + def get_flir_ptu( + self, + idx: int + ) -> FlirPTU: + return self.__flir_ptus.get(idx) + + # FlirPTU: Get All + def get_flir_ptus( + self + ) -> List[FlirPTU]: + return self.__flir_ptus.get_all() + + # FlirPTU: Set + def set_flir_ptu( + self, + flir_ptu: FlirPTU, + ) -> None: + self.__flir_ptus.set(flir_ptu) + + # FlirPTU: Set All + def set_flir_ptus( + self, + flir_ptus: List[FlirPTU] + ) -> None: + self.__flir_ptus.set_all(flir_ptus) - def remove_mount( + # Risers: Add + def add_riser( self, - mount: BaseMount = None, - name: str = None + # By Object + riser: PACS.Riser = None, + # By Parameters + rows: int = None, + columns: int = None, + height: float = None, + thickness: float = PACS.Riser.THICKNESS, + parent: str = Accessory.PARENT, + xyz: List[float] = Accessory.XYZ, + rpy: List[float] = Accessory.RPY, ) -> None: - assert mount or name, ( - "Mount object or name must be passed." + assert riser or ( + rows is not None and ( + columns is not None) and ( + height is not None)), ( + "Riser object or rows, columns, and height must be passed" ) - if name and not mount: - self.__mounts.remove(name) - else: - self.__mounts.remove(mount) + if not riser: + riser = PACS.Riser( + rows, + columns, + height, + thickness, + parent, + xyz, + rpy + ) + self.__pacs_risers.add(riser) + + # Risers: Remove + def remove_riser( + self, + # By Object or Index + riser: PACS.Riser | int, + ) -> None: + self.__pacs_risers.remove(riser) + + # Risers: Get + def get_riser( + self, + idx: int + ) -> PACS.Riser: + return self.__pacs_risers.get(idx) + + # Risers: Get All + def get_risers( + self + ) -> List[PACS.Riser]: + return self.__pacs_risers.get_all() + + # Risers: Set + def set_riser( + self, + riser: PACS.Riser + ) -> None: + self.__pacs_risers.set(riser) + + # Risers: Set All + def set_risers( + self, + risers: List[PACS.Riser] + ) -> None: + self.__pacs_risers.set_all(risers) + + # Brackets: Add + def add_bracket( + self, + # By Object + bracket: PACS.Bracket = None, + # By Parameters + parent: str = "base_link", + model: str = PACS.Bracket.DEFAULT, + extension: float = 0.0, + xyz: List[float] = [0.0, 0.0, 0.0], + rpy: List[float] = [0.0, 0.0, 0.0] + ) -> None: + if not bracket: + bracket = PACS.Bracket( + parent=parent, + model=model, + extension=extension, + xyz=xyz, + rpy=rpy + ) + self.__pacs_brackets.add(bracket) + + # Brackets: Remove + def remove_bracket( + self, + # By Object or Name + bracket: PACS.Bracket | int, + ) -> None: + self.__pacs_brackets.remove(bracket) + + # Bracket: Get + def get_bracket( + self, + idx: int, + ) -> PACS.Bracket: + return self.__pacs_brackets.get(idx) + + # Brackets: Get All + def get_brackets( + self, + ) -> List[PACS.Bracket]: + return self.__pacs_brackets.get_all() + + # Bracket: Set + def set_bracket( + self, + bracket: PACS.Bracket, + ) -> None: + self.__pacs_brackets.set(bracket) + + # Brackets: Set All + def set_brackets( + self, + brackets: List[PACS.Bracket], + ) -> None: + self.__pacs_brackets.set_all(brackets) From d965a62744ade2606c06415e4110c86d397281a4 Mon Sep 17 00:00:00 2001 From: Luis Camero Date: Tue, 4 Apr 2023 10:45:27 -0400 Subject: [PATCH 31/50] Updated sample to new mount iterables --- clearpath_config/sample/a200_config.yaml | 78 ++++++++++++++++-------- 1 file changed, 51 insertions(+), 27 deletions(-) diff --git a/clearpath_config/sample/a200_config.yaml b/clearpath_config/sample/a200_config.yaml index b539995..95f3299 100644 --- a/clearpath_config/sample/a200_config.yaml +++ b/clearpath_config/sample/a200_config.yaml @@ -1,5 +1,5 @@ version: 0 - + system: # These are system level configs self: A200-1 hosts: # These are the hosts that are involved in this system @@ -62,33 +62,57 @@ platform: # These are are parameters specific to a a platform mounts: - - name: "front_pivot" - model: "fath_pivot" - parent: "base_link" - mounting_link: "front_pivot_mount" - angle: 0.0 - xyz: [0.0, 0.0, 0.0] - rpy: [0.0, 0.0, 0.0] + fath_pivot: + - parent: "base_link" + angle: 0.0 + xyz: [0.0, 0.0, 0.0] + rpy: [0.0, 0.0, 0.0] + - parent: "base_link" + angle: 0.0 + xyz: [0.0, 0.0, 0.0] + rpy: [0.0, 0.0, 0.0] + flir_ptu: + - parent: "base_link" + xyz: [0.0, 0.0, 0.0] + rpy: [0.0, 0.0, 0.0] + tty_port: "/dev/ptu" + tcp_port: 4000 + ip: "192.168.131.70" + connection_type: "tty" + limits_enabled: False + - parent: "base_link" + xyz: [0.0, 0.0, 0.0] + rpy: [0.0, 0.0, 0.0] + tty_port: "/dev/ptu" + tcp_port: 4000 + ip: "192.168.131.70" + connection_type: "tty" + limits_enabled: False + riser: + - rows: 7 + columns: 8 + height: 0.10 + thickness: 0.0635 + parent: "base_link" + xyz: [0.0, 0.0, 0.0] + rpy: [0.0, 0.0, 0.0] + - rows: 1 + columns: 1 + height: 0.10 + thickness: 0.0635 + parent: "base_link" + xyz: [0.0, 0.0, 0.0] + rpy: [0.0, 0.0, 0.0] + bracket: + - model: "horizontal" + parent: "base_link" + xyz: [0.0, 0.0, 0.0] + rpy: [0.0, 0.0, 0.0] + - model: "vertical" + parent: "base_link" + xyz: [0.0, 0.0, 0.0] + rpy: [0.0, 0.0, 0.0] - - name: "ptu" - model: "flir_ptu" - parent: "base_link" - mounting_link: "ptu_mount" - xyz: [0.0, 0.0, 0.0] - rpy: [0.0, 0.0, 0.0] - tty_port: "/dev/ptu" - tcp_port: 4000 - ip: "192.168.131.70" - connection_type: "tty" - limits_enabled: False -# front_pivot: -# enabled: true -# parent_link: front_mount -# accessory_link: front_pivot_mount -# model: fath_pivot -# angle: 0 -# xyz: [0, 0, 0] -# rpy: [0, 0, 0] sensors: # Various sensors lidars: From 6c0428b3281635afff36f7c546efc9f74e3b1ec0 Mon Sep 17 00:00:00 2001 From: Luis Camero Date: Tue, 4 Apr 2023 10:47:10 -0400 Subject: [PATCH 32/50] Clear OrderedConfigList if empty list is set --- clearpath_config/common.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/clearpath_config/common.py b/clearpath_config/common.py index 8db61a1..2eef1a6 100644 --- a/clearpath_config/common.py +++ b/clearpath_config/common.py @@ -528,6 +528,9 @@ def set_all( # Copy and Clear tmp_list = deepcopy(self.__list) self.__list.clear() + # If Empty Keep Empty + if not _list: + return # Add One-by-One try: for obj in _list: @@ -535,6 +538,7 @@ def set_all( # Restore Save if Failure except AssertionError: self.__list = tmp_list + self.update() # Name as Unique ID to Index @staticmethod From e8224e8673f227155c3ad70ed101182281b312c4 Mon Sep 17 00:00:00 2001 From: Luis Camero Date: Tue, 4 Apr 2023 10:47:44 -0400 Subject: [PATCH 33/50] BaseMount no longer requires a name, default to index --- clearpath_config/mounts/base.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clearpath_config/mounts/base.py b/clearpath_config/mounts/base.py index be902b8..da7db43 100644 --- a/clearpath_config/mounts/base.py +++ b/clearpath_config/mounts/base.py @@ -7,7 +7,7 @@ class BaseMount(Accessory): def __init__( self, - name: str, + name: str = "base_mount_0", parent: str = Accessory.PARENT, xyz: List[float] = Accessory.XYZ, rpy: List[float] = Accessory.RPY, From d11336d52d6088ebfd25f4595c8ac0fe5233321f Mon Sep 17 00:00:00 2001 From: Luis Camero Date: Tue, 4 Apr 2023 10:48:16 -0400 Subject: [PATCH 34/50] Removed 'pacs_' prefix from brackets and risers --- clearpath_config/mounts/pacs.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/clearpath_config/mounts/pacs.py b/clearpath_config/mounts/pacs.py index 8204951..48150d4 100644 --- a/clearpath_config/mounts/pacs.py +++ b/clearpath_config/mounts/pacs.py @@ -10,7 +10,7 @@ class PACS: MAX_COLUMNS = 7 class Riser(BaseMount): - MOUNT_MODEL = "pacs_riser" + MOUNT_MODEL = "riser" THICKNESS = 0.00635 def __init__( @@ -87,7 +87,7 @@ class Bracket(BaseMount): - allows multiple sensors to be mounted - should be added to any """ - MOUNT_MODEL = "pacs_bracket" + MOUNT_MODEL = "bracket" HORIZONTAL = "horizontal" HORIZONTAL_LARGE = "large" VERTICAL = "vertical" From dd1141aff215d1e7b9137fb9b6d4378320d842d3 Mon Sep 17 00:00:00 2001 From: Luis Camero Date: Tue, 4 Apr 2023 10:49:27 -0400 Subject: [PATCH 35/50] Completely disabled all PACS testing --- clearpath_config/tests/test_platform.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/clearpath_config/tests/test_platform.py b/clearpath_config/tests/test_platform.py index 0431f84..c6758d3 100644 --- a/clearpath_config/tests/test_platform.py +++ b/clearpath_config/tests/test_platform.py @@ -1,5 +1,4 @@ from clearpath_config.platform.decorations import Decorations -from clearpath_config.platform.pacs import PACS from clearpath_config.platform.platform import PlatformConfig, Platform from clearpath_config.tests.test_utils import ( valid_object_variable_check, @@ -176,7 +175,7 @@ def test_top_plate(self): ) assert_not_errors(errors) - +""" # Heights INVALID_HEIGHTS = ["string", -1.0] VALID_HEIGHTS = [0, "0.1", 10.0] @@ -330,7 +329,7 @@ def test_bracket(self): ) assert_not_errors(errors) -""" + def test_full_risers_config(self): errors = [] # Full Risers From bd9f6ef4fded20cc1023ee456ec7c7e93f425eee Mon Sep 17 00:00:00 2001 From: Luis Camero Date: Tue, 4 Apr 2023 10:50:23 -0400 Subject: [PATCH 36/50] Removed name as required argument --- clearpath_config/mounts/mounts.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/clearpath_config/mounts/mounts.py b/clearpath_config/mounts/mounts.py index f0f4839..faac9fa 100644 --- a/clearpath_config/mounts/mounts.py +++ b/clearpath_config/mounts/mounts.py @@ -19,14 +19,14 @@ class Mount(): PACS_BRACKET: PACS.Bracket } - def __new__(cls, name: str, model: str) -> BaseMount: + def __new__(cls, model: str) -> BaseMount: assert model in Mount.MODEL, ( "Model '%s' must be one of: '%s'" % ( model, Mount.MODEL.keys() ) ) - return Mount.MODEL[model](name) + return Mount.MODEL[model]() class MountsConfig: From 016d29453aef19b1b456e89beebd25eceabbf399 Mon Sep 17 00:00:00 2001 From: Luis Camero Date: Tue, 4 Apr 2023 10:50:37 -0400 Subject: [PATCH 37/50] Upgraded parser to match new mounts --- clearpath_config/parser.py | 226 ++++++++++++++++--------------------- 1 file changed, 98 insertions(+), 128 deletions(-) diff --git a/clearpath_config/parser.py b/clearpath_config/parser.py index 851ceed..ca2e419 100644 --- a/clearpath_config/parser.py +++ b/clearpath_config/parser.py @@ -3,7 +3,7 @@ from clearpath_config.mounts.mounts import MountsConfig, Mount, BaseMount, FlirPTU, FathPivot from clearpath_config.platform.base import BaseDecorationsConfig from clearpath_config.platform.decorations import Decorations -from clearpath_config.platform.pacs import PACS +from clearpath_config.mounts.pacs import PACS from clearpath_config.platform.platform import PlatformConfig from clearpath_config.platform.a200 import A200DecorationsConfig from clearpath_config.platform.j100 import J100DecorationsConfig @@ -101,92 +101,6 @@ def __new__(cls, config: dict) -> SystemConfig: return sysconfig -class PACSConfigParser(BaseConfigParser): - # Key - PACS_KEY = "pacs" - # PACS Keys - FULL_RISERS = "full_risers" - ROW_RISERS = "row_risers" - BRACKETS = "brackets" - - @classmethod - def get_full_risers(cls, config: dict) -> List[PACS.FullRiser]: - pacs_full_risers = [] - config_full_risers = cls.get_optional_val(cls.FULL_RISERS, config) - if not config_full_risers: - return [] - assert isinstance(config_full_risers, list), "Full Risers must be a list" - assert all( - [isinstance(entry, dict) for entry in config_full_risers] - ), "Full Risers must be a list of 'dict's" - for riser in config_full_risers: - level = cls.get_required_val("level", riser) - height = cls.get_required_val("height", riser) - pacs_full_risers.append(PACS.FullRiser(level, height)) - return pacs_full_risers - - @classmethod - def get_row_risers(cls, config: dict) -> List[PACS.RowRiser]: - pacs_row_risers = [] - config_row_risers = cls.get_optional_val(cls.ROW_RISERS, config) - if not config_row_risers: - return [] - assert isinstance(config_row_risers, list), "Row Risers must be a list" - assert all( - [isinstance(entry, dict) for entry in config_row_risers] - ), "Row Risers must be a list of 'dict's" - for riser in config_row_risers: - row = cls.get_required_val("row", riser) - level = cls.get_required_val("level", riser) - height = cls.get_required_val("height", riser) - pacs_row_risers.append(PACS.RowRiser(level, row, height)) - return pacs_row_risers - - @classmethod - def get_brackets(cls, config: dict) -> List[PACS.Bracket]: - pacs_brackets = [] - config_brackets = cls.get_optional_val(cls.BRACKETS, config) - if not config_brackets: - return [] - assert isinstance(config_brackets, dict), "Brackets must be a dict" - for name, bracket in config_brackets.items(): - model = cls.get_optional_val("model", bracket, PACS.Bracket.DEFAULT) - parent = cls.get_required_val("parent", bracket) - extension = cls.get_optional_val("extension", bracket, 0.0) - xyz = cls.get_optional_val("xyz", bracket, [0.0, 0.0, 0.0]) - rpy = cls.get_optional_val("rpy", bracket, [0.0, 0.0, 0.0]) - pacs_brackets.append(PACS.Bracket(name, parent, model, extension, xyz, rpy)) - return pacs_brackets - - class A200(BaseConfigParser): - def __new__(cls, config: dict) -> A200DecorationsConfig: - pacsconfig = A200PACSConfig() - # PACS - pacs = cls.get_optional_val(PACSConfigParser.PACS_KEY, config) - if not pacs: - pacsconfig.disable() - return pacsconfig - # PACS.Full_Risers - full_risers = PACSConfigParser.get_full_risers(pacs) - # PACS.Row_Risers - row_risers = PACSConfigParser.get_row_risers(pacs) - # PACS.Brackets - brackets = PACSConfigParser.get_brackets(pacs) - return pacsconfig - - """ - PACS Config - """ - MODEL_CONFIGS = {Platform.A200: A200} - - def __new__(cls, model: str, config: dict): - assert model in cls.MODEL_CONFIGS, "Model '%s' must be one of %s" % ( - model, - cls.MODEL_CONFIGS.keys(), - ) - return cls.MODEL_CONFIGS[model](config) - - class BumperConfigParser(BaseConfigParser): # Bumper Keys ENABLE = "enable" @@ -254,11 +168,6 @@ def __new__(cls, config: dict) -> A200DecorationsConfig: dcnconfig.set_bumper(BumperConfigParser(cls.REAR_BUMPER, decorations)) # Decorations.Top_Plate dcnconfig.set_top_plate(TopPlateConfigParser(cls.TOP_PLATE, decorations)) - # Decorations.PACS - pacs = dcnparser.get_required_val(dcnparser.A200.PACS, decorations) - dcnconfig.set_full_risers(PACSConfigParser.get_full_risers(pacs)) - dcnconfig.set_row_risers(PACSConfigParser.get_row_risers(pacs)) - dcnconfig.set_brackets(PACSConfigParser.get_brackets(pacs)) return dcnconfig class J100: @@ -315,6 +224,7 @@ def __new__(cls, config: dict) -> PlatformConfig: ) return pfmconfig + class AccessoryParser(BaseConfigParser): # Keys NAME = "name" @@ -338,30 +248,20 @@ class Base(BaseConfigParser): MOUNTING_LINK = "mounting_link" def __new__(cls, config: dict) -> BaseMount: - a = AccessoryParser(config) - model = cls.get_required_val( - MountParser.Base.MODEL, - config, - ) - mounting_link = cls.get_optional_val( - MountParser.Base.MOUNTING_LINK, - config, - BaseMount.MOUNTING_LINK, - ) + parent = cls.get_optional_val(AccessoryParser.PARENT, config, Accessory.PARENT) + xyz = cls.get_optional_val(AccessoryParser.XYZ, config, Accessory.XYZ) + rpy = cls.get_optional_val(AccessoryParser.RPY, config, Accessory.RPY) return BaseMount( - name=a.get_name(), - parent=a.get_parent(), - xyz=a.get_xyz(), - rpy=a.get_rpy(), - model=model, - mounting_link=mounting_link, - ) + parent=parent, + xyz=xyz, + rpy=rpy, + ) class FathPivot(BaseConfigParser): # Keys ANGLE = "angle" - def __new__(cls, config:dict) -> FathPivot: + def __new__(cls, config: dict) -> FathPivot: b = MountParser.Base(config) # Pivot Angle angle = cls.get_optional_val( @@ -370,11 +270,9 @@ def __new__(cls, config:dict) -> FathPivot: FathPivot.ANGLE, ) return FathPivot( - name=b.get_name(), parent=b.get_parent(), xyz=b.get_xyz(), rpy=b.get_rpy(), - mounting_link=b.get_mounting_link(), angle=angle, ) @@ -386,7 +284,7 @@ class FlirPTU(BaseConfigParser): CONNECTION_TYPE = "connection_type" LIMITS_ENABLED = "limits_enabled" - def __new__(cls, config: dict) -> BaseMount: + def __new__(cls, config: dict) -> FlirPTU: b = MountParser.Base(config) # TTY Port tty_port = cls.get_optional_val( @@ -419,11 +317,9 @@ def __new__(cls, config: dict) -> BaseMount: FlirPTU.LIMITS_ENABLED, ) return FlirPTU( - name=b.get_name(), parent=b.get_parent(), xyz=b.get_xyz(), rpy=b.get_rpy(), - mounting_link=b.get_mounting_link(), tty_port=tty_port, tcp_port=tcp_port, ip=ip, @@ -431,14 +327,78 @@ def __new__(cls, config: dict) -> BaseMount: limits_enabled=limits_enabled, ) + class PACSRiser(BaseConfigParser): + # Keys + ROWS = "rows" + COLUMNS = "columns" + HEIGHT = "height" + THICKNESS = "thickness" + + def __new__(cls, config: dict) -> PACS.Riser: + b = MountParser.Base(config) + # Rows + rows = cls.get_required_val( + MountParser.PACSRiser.ROWS, + config + ) + # Columns + columns = cls.get_required_val( + MountParser.PACSRiser.COLUMNS, + config + ) + # Height + height = cls.get_required_val( + MountParser.PACSRiser.HEIGHT, + config + ) + # Thickness + thickness = cls.get_optional_val( + MountParser.PACSRiser.THICKNESS, + config, + PACS.Riser.THICKNESS + ) + return PACS.Riser( + rows=rows, + columns=columns, + height=height, + thickness=thickness, + parent=b.get_parent(), + xyz=b.get_xyz(), + rpy=b.get_rpy(), + ) + + class PACSBracket(BaseConfigParser): + # Keys + MODEL = "model" + + def __new__(cls, config: dict) -> PACS.Riser: + b = MountParser.Base(config) + # Model + model = cls.get_optional_val( + MountParser.PACSBracket.MODEL, + config, + PACS.Bracket.DEFAULT + ) + return PACS.Bracket( + model=model, + parent=b.get_parent(), + xyz=b.get_xyz(), + rpy=b.get_rpy(), + ) + MODELS = { Mount.FATH_PIVOT: FathPivot, Mount.FLIR_PTU: FlirPTU, + Mount.PACS_RISER: PACSRiser, + Mount.PACS_BRACKET: PACSBracket } - def __new__(cls, config: dict) -> BaseMount: - model = cls.get_required_val( - MountParser.Base.MODEL, - config + + def __new__(cls, model, config: dict) -> BaseMount: + assert model in MountParser.MODELS, ( + "Model '%s' must be one of '%s'" % ( + model, + MountParser.MODELS + ) ) return cls.MODELS[model](config) @@ -452,17 +412,27 @@ def __new__(cls, config: dict) -> MountsConfig: mntconfig = MountsConfig() # Mounts mounts = cls.get_optional_val(cls.MOUNTS, config) - mntconfig.set_mounts(cls.get_mounts(mounts)) + mntconfig.set_fath_pivots(cls.get_mounts(mounts, Mount.FATH_PIVOT)) + mntconfig.set_flir_ptus(cls.get_mounts(mounts, Mount.FLIR_PTU)) + mntconfig.set_risers(cls.get_mounts(mounts, Mount.PACS_RISER)) + mntconfig.set_brackets(cls.get_mounts(mounts, Mount.PACS_BRACKET)) return mntconfig - @staticmethod - def get_mounts(config: list) -> List[Mount]: - # Assert List of Dictionaries - assert (isinstance(config, list) - ), "Config must be a list of dictionaries" - assert ((all([isinstance(c, dict) for c in config])) - ), "Config must be a list of dictionaries" - return [MountParser(c) for c in config] + @classmethod + def get_mounts(cls, config: dict, model: str) -> List[Mount]: + # Assert Dictionary + assert isinstance(config, dict), ( + "Mounts must be a dictionary of lists" + ) + entries = cls.get_optional_val(model, config, []) + # Assert List + assert isinstance(entries, list), ( + "Model entries must be in a list" + ) + models = [] + for entry in entries: + models.append(MountParser(model, entry)) + return models # Clearpath Configuration Parser From 03594b9016bcc8cc6d6ddff56fd45daef6d55619 Mon Sep 17 00:00:00 2001 From: Luis Camero Date: Tue, 4 Apr 2023 14:43:52 -0400 Subject: [PATCH 38/50] Fixed key error print statement --- clearpath_config/parser.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clearpath_config/parser.py b/clearpath_config/parser.py index ca2e419..e9831b6 100644 --- a/clearpath_config/parser.py +++ b/clearpath_config/parser.py @@ -22,7 +22,7 @@ def check_key_exists(key: str, config: dict) -> bool: def assert_key_exists(key: str, config: dict) -> None: assert BaseConfigParser.check_key_exists( key, config - ), "Key '%s' must be in YAML" + ), "Key '%s' must be in YAML" % key @staticmethod def get_required_val(key: str, config: dict): From 1f3f85fe46a8c7da43defa311d350f18412b5a71 Mon Sep 17 00:00:00 2001 From: Luis Camero Date: Tue, 4 Apr 2023 14:49:03 -0400 Subject: [PATCH 39/50] Removed and --- clearpath_config/mounts/generic.py | 27 +++++++++++++++++++++++++++ clearpath_config/mounts/mounts.py | 7 +------ clearpath_config/mounts/pacs.py | 23 ++--------------------- clearpath_config/parser.py | 7 ------- 4 files changed, 30 insertions(+), 34 deletions(-) create mode 100644 clearpath_config/mounts/generic.py diff --git a/clearpath_config/mounts/generic.py b/clearpath_config/mounts/generic.py new file mode 100644 index 0000000..d30c607 --- /dev/null +++ b/clearpath_config/mounts/generic.py @@ -0,0 +1,27 @@ +from clearpath_config.common import Accessory +from typing import List + + +# Generic +class Generic: + class Box(Accessory): + def __init__( + self, + name: str, + parent: str = ..., + xyz: List[float] = ..., + rpy: List[float] = ... + ) -> None: + super().__init__(name, parent, xyz, rpy) + + # Cylinder + class Cylinder(Accessory): + def __init__( + self, + name: str, + parent: str = ..., + xyz: List[float] = ..., + rpy: List[float] = ... + ) -> None: + super().__init__(name, parent, xyz, rpy) + # Sphere diff --git a/clearpath_config/mounts/mounts.py b/clearpath_config/mounts/mounts.py index faac9fa..35154b8 100644 --- a/clearpath_config/mounts/mounts.py +++ b/clearpath_config/mounts/mounts.py @@ -186,7 +186,6 @@ def add_riser( # By Parameters rows: int = None, columns: int = None, - height: float = None, thickness: float = PACS.Riser.THICKNESS, parent: str = Accessory.PARENT, xyz: List[float] = Accessory.XYZ, @@ -194,15 +193,13 @@ def add_riser( ) -> None: assert riser or ( rows is not None and ( - columns is not None) and ( - height is not None)), ( + columns is not None)), ( "Riser object or rows, columns, and height must be passed" ) if not riser: riser = PACS.Riser( rows, columns, - height, thickness, parent, xyz, @@ -253,7 +250,6 @@ def add_bracket( # By Parameters parent: str = "base_link", model: str = PACS.Bracket.DEFAULT, - extension: float = 0.0, xyz: List[float] = [0.0, 0.0, 0.0], rpy: List[float] = [0.0, 0.0, 0.0] ) -> None: @@ -261,7 +257,6 @@ def add_bracket( bracket = PACS.Bracket( parent=parent, model=model, - extension=extension, xyz=xyz, rpy=rpy ) diff --git a/clearpath_config/mounts/pacs.py b/clearpath_config/mounts/pacs.py index 48150d4..3b5b3fa 100644 --- a/clearpath_config/mounts/pacs.py +++ b/clearpath_config/mounts/pacs.py @@ -17,7 +17,6 @@ def __init__( self, rows: int, columns: int, - height: float, thickness: float = THICKNESS, parent: str = Accessory.PARENT, xyz: List[float] = Accessory.XYZ, @@ -33,8 +32,6 @@ def __init__( self.set_rows(rows) self.columns: int = 0 self.set_columns(columns) - self.height: float = 0.0 - self.set_height(height) self.thickness: float = 0.0 self.set_thickness(thickness) @@ -59,9 +56,9 @@ def set_columns(self, columns: int): assert isinstance(columns, int), ( "Riser columns must be an integer." ) - assert 0 < columns <= PACS.MAX_ROWS, ( + assert 0 < columns <= PACS.MAX_COLUMNS, ( "Riser rows must be between %s and %s" % ( - 0, PACS.MAX_ROWS + 0, PACS.MAX_COLUMNS ) ) self.columns = columns @@ -98,7 +95,6 @@ def __init__( self, parent: str = "base_link", model: str = DEFAULT, - extension: float = 0.0, xyz: List[float] = [0.0, 0.0, 0.0], rpy: List[float] = [0.0, 0.0, 0.0], ) -> None: @@ -113,10 +109,6 @@ def __init__( self.model = PACS.Bracket.DEFAULT if model: self.set_model(model) - # Extension: length of standoffs in meters - self.extension = 0.0 - if extension: - self.set_extension(extension) def get_model(self) -> str: return self.model @@ -127,14 +119,3 @@ def set_model(self, model: str) -> None: "it must be one of the following: %s" % self.MODELS ]) self.model = model - - def get_extension(self) -> float: - return self.extension - - def set_extension(self, extension) -> None: - try: - extension = float(extension) - except ValueError as e: - raise AssertionError(e.args[0]) - assert extension >= 0, "Bracket extension must be a positive value" - self.extension = extension diff --git a/clearpath_config/parser.py b/clearpath_config/parser.py index e9831b6..ef874e0 100644 --- a/clearpath_config/parser.py +++ b/clearpath_config/parser.py @@ -331,7 +331,6 @@ class PACSRiser(BaseConfigParser): # Keys ROWS = "rows" COLUMNS = "columns" - HEIGHT = "height" THICKNESS = "thickness" def __new__(cls, config: dict) -> PACS.Riser: @@ -346,11 +345,6 @@ def __new__(cls, config: dict) -> PACS.Riser: MountParser.PACSRiser.COLUMNS, config ) - # Height - height = cls.get_required_val( - MountParser.PACSRiser.HEIGHT, - config - ) # Thickness thickness = cls.get_optional_val( MountParser.PACSRiser.THICKNESS, @@ -360,7 +354,6 @@ def __new__(cls, config: dict) -> PACS.Riser: return PACS.Riser( rows=rows, columns=columns, - height=height, thickness=thickness, parent=b.get_parent(), xyz=b.get_xyz(), From 729ea1f8ace1c650b328d29b764ddf2d0220ff4d Mon Sep 17 00:00:00 2001 From: Luis Camero Date: Tue, 4 Apr 2023 14:58:27 -0400 Subject: [PATCH 40/50] Updated Husky sampl --- clearpath_config/sample/a200_config.yaml | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/clearpath_config/sample/a200_config.yaml b/clearpath_config/sample/a200_config.yaml index 95f3299..10996fa 100644 --- a/clearpath_config/sample/a200_config.yaml +++ b/clearpath_config/sample/a200_config.yaml @@ -89,17 +89,15 @@ mounts: connection_type: "tty" limits_enabled: False riser: - - rows: 7 - columns: 8 - height: 0.10 - thickness: 0.0635 + - rows: 8 + columns: 7 + thickness: 0.00635 parent: "base_link" xyz: [0.0, 0.0, 0.0] rpy: [0.0, 0.0, 0.0] - rows: 1 columns: 1 - height: 0.10 - thickness: 0.0635 + thickness: 0.00635 parent: "base_link" xyz: [0.0, 0.0, 0.0] rpy: [0.0, 0.0, 0.0] From a2e128f73f4a8b7505db88f7ec54e115297b0911 Mon Sep 17 00:00:00 2001 From: Luis Camero Date: Tue, 4 Apr 2023 15:03:44 -0400 Subject: [PATCH 41/50] Added __init__ to mounts --- clearpath_config/mounts/__init__.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 clearpath_config/mounts/__init__.py diff --git a/clearpath_config/mounts/__init__.py b/clearpath_config/mounts/__init__.py new file mode 100644 index 0000000..e69de29 From 7f8cf03af9ecc00c78a53ab442408a3bec95ce66 Mon Sep 17 00:00:00 2001 From: Luis Camero Date: Tue, 4 Apr 2023 16:44:44 -0400 Subject: [PATCH 42/50] Added BaseDecoration; by default disabled --- clearpath_config/parser.py | 101 ++++++---- clearpath_config/platform/a200.py | 14 +- clearpath_config/platform/base.py | 46 ++--- clearpath_config/platform/decorations.py | 228 +++++++++++------------ clearpath_config/platform/j100.py | 10 +- clearpath_config/sample/a200_config.yaml | 29 --- 6 files changed, 208 insertions(+), 220 deletions(-) diff --git a/clearpath_config/parser.py b/clearpath_config/parser.py index ef874e0..2aae4f7 100644 --- a/clearpath_config/parser.py +++ b/clearpath_config/parser.py @@ -1,8 +1,20 @@ -from clearpath_config.common import Platform, Accessory +from clearpath_config.common import ( + Platform, + Accessory +) from clearpath_config.clearpath_config import ClearpathConfig -from clearpath_config.mounts.mounts import MountsConfig, Mount, BaseMount, FlirPTU, FathPivot +from clearpath_config.mounts.mounts import ( + MountsConfig, + Mount, + BaseMount, + FlirPTU, + FathPivot +) from clearpath_config.platform.base import BaseDecorationsConfig -from clearpath_config.platform.decorations import Decorations +from clearpath_config.platform.decorations import ( + Bumper, + TopPlate +) from clearpath_config.mounts.pacs import PACS from clearpath_config.platform.platform import PlatformConfig from clearpath_config.platform.a200 import A200DecorationsConfig @@ -103,18 +115,18 @@ def __new__(cls, config: dict) -> SystemConfig: class BumperConfigParser(BaseConfigParser): # Bumper Keys - ENABLE = "enable" + ENABLED = "enabled" EXTENSION = "extension" MODEL = "model" - def __new__(cls, key: str, config: dict) -> Decorations.Bumper: - bmpconfig = Decorations.Bumper(key) + def __new__(cls, key: str, config: dict) -> Bumper: + bmpconfig = Bumper(key) # Bumper bumper = cls.get_optional_val(key, config) if not bumper: return bmpconfig # Bumper.Enable - if cls.get_required_val(cls.ENABLE, bumper): + if cls.get_optional_val(cls.ENABLED, bumper, False): bmpconfig.enable() else: bmpconfig.disable() @@ -127,17 +139,17 @@ def __new__(cls, key: str, config: dict) -> Decorations.Bumper: class TopPlateConfigParser(BaseConfigParser): # Top Plate Keys - ENABLE = "enable" + ENABLED = "enabled" MODEL = "model" - def __new__(cls, key: str, config: dict) -> Decorations.TopPlate: - topconfig = Decorations.TopPlate(key) + def __new__(cls, key: str, config: dict) -> TopPlate: + topconfig = TopPlate(key) # Top_Plate top_plate = cls.get_optional_val(key, config) if not top_plate: return topconfig # Top_Plate.Enable - if cls.get_required_val(cls.ENABLE, top_plate): + if cls.get_required_val(cls.ENABLED, top_plate): topconfig.enable() else: topconfig.disable() @@ -161,13 +173,17 @@ def __new__(cls, config: dict) -> A200DecorationsConfig: dcnconfig = A200DecorationsConfig() dcnparser = DecorationsConfigParser # Decorations - decorations = dcnparser.get_required_val(dcnparser.DECORATIONS, config) + decorations = ( + dcnparser.get_required_val(dcnparser.DECORATIONS, config)) # Decorations.Front_Bumper - dcnconfig.set_bumper(BumperConfigParser(cls.FRONT_BUMPER, decorations)) + dcnconfig.set_bumper( + BumperConfigParser(cls.FRONT_BUMPER, decorations)) # Decorations.Rear_Bumper - dcnconfig.set_bumper(BumperConfigParser(cls.REAR_BUMPER, decorations)) + dcnconfig.set_bumper( + BumperConfigParser(cls.REAR_BUMPER, decorations)) # Decorations.Top_Plate - dcnconfig.set_top_plate(TopPlateConfigParser(cls.TOP_PLATE, decorations)) + dcnconfig.set_top_plate( + TopPlateConfigParser(cls.TOP_PLATE, decorations)) return dcnconfig class J100: @@ -179,11 +195,14 @@ def __new__(cls, config: dict) -> J100DecorationsConfig: dcnconfig = J100DecorationsConfig() dcnparser = DecorationsConfigParser # Decorations - decorations = dcnparser.get_required_val(dcnparser.DECORATIONS, config) + decorations = ( + dcnparser.get_required_val(dcnparser.DECORATIONS, config)) # Decorations.Front_Bumper - dcnconfig.set_bumper(BumperConfigParser(cls.FRONT_BUMPER, decorations)) + dcnconfig.set_bumper( + BumperConfigParser(cls.FRONT_BUMPER, decorations)) # Decorations.Rear_Bumper - dcnconfig.set_bumper(BumperConfigParser(cls.REAR_BUMPER, decorations)) + dcnconfig.set_bumper( + BumperConfigParser(cls.REAR_BUMPER, decorations)) return dcnconfig MODEL_CONFIGS = {Platform.A200: A200, Platform.J100: J100} @@ -212,16 +231,18 @@ def __new__(cls, config: dict) -> PlatformConfig: # Platform platform = cls.get_required_val(cls.PLATFORM, config) # Platform.SerialNumber - pfmconfig.set_serial_number(cls.get_required_val(cls.SERIAL_NUMBER, platform)) + pfmconfig.set_serial_number( + cls.get_required_val(cls.SERIAL_NUMBER, platform)) # Platform.Decorations - pfmconfig.decorations = DecorationsConfigParser(pfmconfig.get_model(), platform) + pfmconfig.decorations = ( + DecorationsConfigParser(pfmconfig.get_model(), platform)) # Platform.Extras extras = cls.get_optional_val(cls.EXTRAS, platform) if extras: - pfmconfig.extras.set_urdf_extras(cls.get_optional_val(cls.URDF, extras, "")) + pfmconfig.extras.set_urdf_extras( + cls.get_optional_val(cls.URDF, extras, "")) pfmconfig.extras.set_control_extras( - cls.get_optional_val(cls.CONTROL, extras, "") - ) + cls.get_optional_val(cls.CONTROL, extras, "")) return pfmconfig @@ -233,10 +254,14 @@ class AccessoryParser(BaseConfigParser): RPY = "rpy" def __new__(cls, config: dict) -> Accessory: - name = cls.get_required_val(AccessoryParser.NAME, config) - parent = cls.get_optional_val(AccessoryParser.PARENT, config, Accessory.PARENT) - xyz = cls.get_optional_val(AccessoryParser.XYZ, config, Accessory.XYZ) - rpy = cls.get_optional_val(AccessoryParser.RPY, config, Accessory.RPY) + name = cls.get_required_val( + AccessoryParser.NAME, config) + parent = cls.get_optional_val( + AccessoryParser.PARENT, config, Accessory.PARENT) + xyz = cls.get_optional_val( + AccessoryParser.XYZ, config, Accessory.XYZ) + rpy = cls.get_optional_val( + AccessoryParser.RPY, config, Accessory.RPY) return Accessory(name, parent, xyz, rpy) @@ -248,9 +273,12 @@ class Base(BaseConfigParser): MOUNTING_LINK = "mounting_link" def __new__(cls, config: dict) -> BaseMount: - parent = cls.get_optional_val(AccessoryParser.PARENT, config, Accessory.PARENT) - xyz = cls.get_optional_val(AccessoryParser.XYZ, config, Accessory.XYZ) - rpy = cls.get_optional_val(AccessoryParser.RPY, config, Accessory.RPY) + parent = cls.get_optional_val( + AccessoryParser.PARENT, config, Accessory.PARENT) + xyz = cls.get_optional_val( + AccessoryParser.XYZ, config, Accessory.XYZ) + rpy = cls.get_optional_val( + AccessoryParser.RPY, config, Accessory.RPY) return BaseMount( parent=parent, xyz=xyz, @@ -437,7 +465,8 @@ def find_valid_path(path, cwd=None): if cwd: relpath = os.path.join(cwd, path) else: - relpath = os.path.join(os.path.dirname(os.path.abspath(__file__)), path) + relpath = os.path.join( + os.path.dirname(os.path.abspath(__file__)), path) if not os.path.isfile(abspath) and not os.path.isfile(relpath): return None if os.path.isfile(abspath): @@ -455,13 +484,15 @@ def read_yaml(path: str) -> dict: try: config = yaml.load(open(path), Loader=yaml.SafeLoader) except yaml.scanner.ScannerError: - raise AssertionError("YAML file '%s' is not well formed" % path) + raise AssertionError( + "YAML file '%s' is not well formed" % path) except yaml.constructor.ConstructorError: raise AssertionError( - "YAML file '%s' is attempting to create unsafe objects" % path - ) + "YAML file '%s' is attempting to create unsafe objects" % ( + path)) # Check contents are a Dictionary - assert isinstance(config, dict), "YAML file '%s' is not a dictionary" % path + assert isinstance(config, dict), ( + "YAML file '%s' is not a dictionary" % path) return config @staticmethod diff --git a/clearpath_config/platform/a200.py b/clearpath_config/platform/a200.py index 70b6b9d..c8c96fa 100644 --- a/clearpath_config/platform/a200.py +++ b/clearpath_config/platform/a200.py @@ -1,7 +1,7 @@ # A200 Husky Platform Configuration from clearpath_config.common import Platform from clearpath_config.platform.base import BaseDecorationsConfig -from clearpath_config.platform.decorations import Decorations +from clearpath_config.platform.decorations import Bumper, TopPlate # A200 Husky Decorations Configuration @@ -12,20 +12,20 @@ def __init__(self) -> None: # Front Bumper self.add_bumper( name="front_bumper", - enable=True, + enabled=True, extension=0.0, - model=Decorations.Bumper.DEFAULT + model=Bumper.DEFAULT ) # Rear Bumper self.add_bumper( name="rear_bumper", - enable=True, + enabled=True, extension=0.0, - model=Decorations.Bumper.DEFAULT + model=Bumper.DEFAULT ) # Top Plate self.add_top_plate( name="top_plate", - enable=True, - model=Decorations.TopPlate.DEFAULT + enabled=True, + model=TopPlate.DEFAULT ) diff --git a/clearpath_config/platform/base.py b/clearpath_config/platform/base.py index ab965dd..6c0c79f 100644 --- a/clearpath_config/platform/base.py +++ b/clearpath_config/platform/base.py @@ -1,5 +1,5 @@ from clearpath_config.common import ListConfig, Platform -from clearpath_config.platform.decorations import Decorations +from clearpath_config.platform.decorations import Bumper, TopPlate from typing import List @@ -21,29 +21,29 @@ def __init__(self, model) -> None: ) # Standard Platform Decorations self.__bumpers = ListConfig[ - Decorations.Bumper, str]( + Bumper, str]( uid=ListConfig.uid_name) self.__top_plates = ListConfig[ - Decorations.TopPlate, str]( + TopPlate, str]( uid=ListConfig.uid_name) # Bumper: Add def add_bumper( self, # By Object - bumper: Decorations.Bumper = None, + bumper: Bumper = None, # By Parameters name: str = None, - enable: bool = True, + enabled: bool = True, extension: float = 0.0, - model: str = Decorations.Bumper.DEFAULT, + model: str = Bumper.DEFAULT, ) -> None: assert bumper or name, "Bumper object or name must be passed" # Create Object if name and not bumper: - bumper = Decorations.Bumper( + bumper = Bumper( name=name, - enable=enable, + enabled=enabled, extension=extension, model=model ) @@ -53,7 +53,7 @@ def add_bumper( def remove_bumper( self, # By Object or Name - bumper: Decorations.Bumper | str, + bumper: Bumper | str, ) -> None: self.__bumpers.remove(bumper) @@ -61,26 +61,26 @@ def remove_bumper( def get_bumper( self, name: str, - ) -> Decorations.Bumper: + ) -> Bumper: return self.__bumpers.get(name) # Bumper: Get All def get_bumpers( self - ) -> List[Decorations.Bumper]: + ) -> List[Bumper]: return self.__bumpers.get_all() # Bumper: Set def set_bumper( self, - bumper: Decorations.Bumper, + bumper: Bumper, ) -> None: self.__bumpers.set(bumper) # Bumper: Set All def set_bumpers( self, - bumpers: List[Decorations.Bumper], + bumpers: List[Bumper], ) -> None: self.__bumpers.set_all(bumpers) @@ -88,17 +88,17 @@ def set_bumpers( def add_top_plate( self, # By Object - top_plate: Decorations.TopPlate = None, + top_plate: TopPlate = None, # By Parameters name: str = None, - enable: bool = True, - model: str = Decorations.TopPlate.DEFAULT, + enabled: bool = True, + model: str = TopPlate.DEFAULT, ) -> None: assert top_plate or name, "Top plate object or name must be passed." if name and not top_plate: - top_plate = Decorations.TopPlate( + top_plate = TopPlate( name=name, - enable=enable, + enabled=enabled, model=model ) self.__top_plates.add(top_plate) @@ -107,7 +107,7 @@ def add_top_plate( def remove_top_plate( self, # By Object or Name - top_plate: Decorations.TopPlate | str, + top_plate: TopPlate | str, ) -> None: self.__top_plates.remove(top_plate) @@ -115,25 +115,25 @@ def remove_top_plate( def get_top_plate( self, name: str - ) -> Decorations.TopPlate: + ) -> TopPlate: return self.__top_plates.get(name) # Top Plate: Get All def get_top_plates( self - ) -> List[Decorations.TopPlate]: + ) -> List[TopPlate]: return self.__top_plates.get_all() # Top Plate: Set def set_top_plate( self, - top_plate: Decorations.TopPlate + top_plate: TopPlate ) -> None: self.__top_plates.set(top_plate) # Top Plate: Set All def set_top_plates( self, - top_plates: List[Decorations.TopPlate] + top_plates: List[TopPlate] ) -> None: self.__top_plates.set_all(top_plates) diff --git a/clearpath_config/platform/decorations.py b/clearpath_config/platform/decorations.py index 1ae36c8..11acbe7 100644 --- a/clearpath_config/platform/decorations.py +++ b/clearpath_config/platform/decorations.py @@ -1,122 +1,108 @@ -# DecorationAccessories -class Decorations: - # General Decorations - class Bumper: - """ - Bumpers on the Husky can be: - - toggled on/off - - extended - - swapped for a Wibotic charger bumper - """ - - DEFAULT = "default" - WIBOTIC = "wibotic" - MODELS = [DEFAULT, WIBOTIC] - - def __init__( - self, - name: str, - enable: bool = True, - extension: float = 0.0, - model: str = DEFAULT - ) -> None: - self.name = name - self.enabled = True - self.extension = 0.0 - self.model = self.DEFAULT - if enable: - self.enable() - if extension: - self.set_extension(extension) - if model: - self.set_model(model) - - def get_name(self) -> str: - return self.name - - def set_name(self, name) -> None: - self.name = name - - def enable(self) -> None: - self.enabled = True - - def disable(self) -> None: - self.enabled = False - - def get_extension(self) -> float: - return self.extension - - def set_extension(self, extension) -> None: - try: - extension = float(extension) - except ValueError as e: - raise AssertionError(e.args[0]) - assert isinstance( - extension, float - ), " ".join([ - "Bumper extension must be of type float,", - " unexpected type '%s'" % type(extension) - ]) - assert extension >= 0, "Bumper extension must be a positive value" - self.extension = extension - - def get_model(self) -> str: - return self.model - - def set_model(self, model: str) -> None: - assert model in self.MODELS, ( - "Bumper model '%s' is not one of: %s" % ( - model, - self.MODELS, - ) +class BaseDecoration: + """ + BaseDecoration + - enable: whether decoration is enabled or not + - model: what type of that decoration it is + """ + DECORATION_MODEL = "base_decoration" + DEFAULT = "default" + MODELS = [DEFAULT] + + def __init__( + self, + name: str = DECORATION_MODEL, + enabled: bool = False, + model: str = DEFAULT + ) -> None: + self.enabled: bool = bool(enabled) + self.name: str = BaseDecoration.DECORATION_MODEL + self.set_name(name) + self.model: str = BaseDecoration.DEFAULT + self.set_model(model) + + def get_name(self) -> str: + return self.name + + def set_name(self, name) -> None: + self.name = name + + def enable(self) -> None: + self.enabled = True + + def disable(self) -> None: + self.enabled = False + + def get_model(self) -> str: + return self.model + + def set_model(self, model: str) -> None: + assert model in self.MODELS, ( + "%s model '%s' is not one of: '%s'" % ( + self.DECORATION_MODEL.title(), + model, + self.MODELS, ) - self.model = model - - class TopPlate: - """ - Top Plate on the Husky can be: - - toggled on/off - - swapped for larger plate and pacs plate - - PACS plate is required - """ - - DEFAULT = "default" - LARGE = "large" - PACS = "pacs" - MODELS = [DEFAULT, LARGE, PACS] - - def __init__( - self, - name: str, - enable: bool = True, - model: str = DEFAULT - ) -> None: - self.name = name - self.enabled = True - self.extension = 0.0 - self.model = self.DEFAULT - if enable: - self.enable() - if model: - self.set_model(model) - - def set_name(self, name: str) -> None: - self.name = name - - def get_name(self) -> str: - return self.name - - def enable(self) -> None: - self.enabled = True - - def disable(self) -> None: - self.enabled = False - - def get_model(self) -> str: - return self.model - - def set_model(self, model: str) -> None: - assert ( - model in self.MODELS - ), "Top plate model '%s' is not one of: %s" % (model, self.MODELS) - self.model = model + ) + self.model = model + + +class Bumper(BaseDecoration): + """ + Bumper + - enabled: can be toggled + - model: can be swapped to a Wibotic charger bumper + - extension: meters by which it is extended + """ + DECORATION_MODEL = "bumper" + DEFAULT = "default" + WIBOTIC = "wibotic" + MODELS = [DEFAULT, WIBOTIC] + + def __init__( + self, + name: str = DECORATION_MODEL, + enabled: bool = False, + model: str = DEFAULT, + extension: float = 0.0, + ) -> None: + super().__init__(name, enabled, model) + self.extension: float = 0.0 + self.set_extension(extension) + + def get_extension(self) -> float: + return self.extension + + def set_extension(self, extension: float) -> None: + try: + extension = float(extension) + except ValueError as e: + raise AssertionError(e.args[0]) + assert isinstance( + extension, float + ), " ".join([ + "Bumper extension must be of type float,", + " unexpected type '%s'" % type(extension) + ]) + assert extension >= 0, "Bumper extension must be a positive value" + self.extension = extension + + +class TopPlate(BaseDecoration): + """ + TopPlate + - enabled: can be toggled + - model: can be swapped to a large or PACS plate + """ + DECORATION_MODEL = "top_plate" + DEFAULT = "default" + LARGE = "large" + PACS = "pacs" + MODELS = [DEFAULT, LARGE, PACS] + + def __init__( + self, + name: str = DECORATION_MODEL, + enabled: bool = False, + model: str = DEFAULT + ) -> None: + super().__init__(name, enabled, model) diff --git a/clearpath_config/platform/j100.py b/clearpath_config/platform/j100.py index f92f29a..3f2ce52 100644 --- a/clearpath_config/platform/j100.py +++ b/clearpath_config/platform/j100.py @@ -1,7 +1,7 @@ # J100 Jackal Platform Configuration from clearpath_config.common import Platform from clearpath_config.platform.base import BaseDecorationsConfig -from clearpath_config.platform.decorations import Decorations +from clearpath_config.platform.decorations import Bumper, TopPlate # J100 Jackal Decorations Configuration @@ -11,14 +11,14 @@ def __init__(self) -> None: # Front Bumper self.add_bumper( name="front_bumper", - enable=True, + enabled=True, extension=0.0, - model=Decorations.Bumper.DEFAULT + model=Bumper.DEFAULT ) # Rear Bumper self.add_bumper( name="rear_bumper", - enable=True, + enabled=True, extension=0.0, - model=Decorations.Bumper.DEFAULT + model=Bumper.DEFAULT ) diff --git a/clearpath_config/sample/a200_config.yaml b/clearpath_config/sample/a200_config.yaml index 10996fa..0017b95 100644 --- a/clearpath_config/sample/a200_config.yaml +++ b/clearpath_config/sample/a200_config.yaml @@ -20,42 +20,13 @@ platform: # These are are parameters specific to a a platform serial_number: cpr-a200-0001 decorations: # Platform specific accessories front_bumper: - enable: true extension: 0.1 model: wibotic rear_bumper: - enable: true extension: 0.2 model: default top_plate: - enable: false model: pacs - pacs: - full_risers: - - level: 1 - height: 0.5 - - level: 2 - height: 0.5 - row_risers: - - row: 1 - level: 1 - height: 0.5 - - row: 2 - level: 1 - height: 0.5 - brackets: - a01_bracket: - model: horizontal - extension: 0.0 - parent: "A01" - xyz: [0.0, 0.0, 0.2] - rpy: [0.0, 0.0, 0.0] - a02_bracket: - model: horizontal - extension: 0.0 - parent: A02 - xyz: [0.0, 0.0, 0.0] - rpy: [0.0, 0.0, 0.0] extras: urdf: /fake/path/empty.urdf.xacro control: /fake/path/empty.yaml From b3c07c41b6829e165ca4f1ea6a0a8207153dd055 Mon Sep 17 00:00:00 2001 From: Luis Camero Date: Tue, 4 Apr 2023 16:46:26 -0400 Subject: [PATCH 43/50] Fixed top plate in parser --- clearpath_config/parser.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clearpath_config/parser.py b/clearpath_config/parser.py index 2aae4f7..cf01815 100644 --- a/clearpath_config/parser.py +++ b/clearpath_config/parser.py @@ -149,7 +149,7 @@ def __new__(cls, key: str, config: dict) -> TopPlate: if not top_plate: return topconfig # Top_Plate.Enable - if cls.get_required_val(cls.ENABLED, top_plate): + if cls.get_optional_val(cls.ENABLED, top_plate, False): topconfig.enable() else: topconfig.disable() From ec952d29cb51bdd60263117507de9def47b6a0b4 Mon Sep 17 00:00:00 2001 From: Luis Camero Date: Tue, 4 Apr 2023 16:47:59 -0400 Subject: [PATCH 44/50] Set decorations to enabled if not specified but exist --- clearpath_config/parser.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/clearpath_config/parser.py b/clearpath_config/parser.py index cf01815..1e0b349 100644 --- a/clearpath_config/parser.py +++ b/clearpath_config/parser.py @@ -126,7 +126,7 @@ def __new__(cls, key: str, config: dict) -> Bumper: if not bumper: return bmpconfig # Bumper.Enable - if cls.get_optional_val(cls.ENABLED, bumper, False): + if cls.get_optional_val(cls.ENABLED, bumper, True): bmpconfig.enable() else: bmpconfig.disable() @@ -149,7 +149,7 @@ def __new__(cls, key: str, config: dict) -> TopPlate: if not top_plate: return topconfig # Top_Plate.Enable - if cls.get_optional_val(cls.ENABLED, top_plate, False): + if cls.get_optional_val(cls.ENABLED, top_plate, True): topconfig.enable() else: topconfig.disable() From a42ed01468770f9a856855d427b216e79623a73c Mon Sep 17 00:00:00 2001 From: Luis Camero Date: Tue, 4 Apr 2023 17:11:06 -0400 Subject: [PATCH 45/50] Added method to retrieve all mounts --- clearpath_config/mounts/mounts.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/clearpath_config/mounts/mounts.py b/clearpath_config/mounts/mounts.py index 35154b8..11ac552 100644 --- a/clearpath_config/mounts/mounts.py +++ b/clearpath_config/mounts/mounts.py @@ -60,6 +60,15 @@ def __init__(self) -> None: ) ) + # Get All Mounts + def get_all_mounts(self) -> List[BaseMount]: + mounts = [] + mounts.extend(self.get_fath_pivots()) + mounts.extend(self.get_flir_ptus()) + mounts.extend(self.get_risers()) + mounts.extend(self.get_brackets()) + return mounts + # FathPivot: Add def add_fath_pivot( self, From d3bb6bce537e1179cb27ee4ac9f3efb91e0eeed5 Mon Sep 17 00:00:00 2001 From: Luis Camero Date: Tue, 4 Apr 2023 17:39:11 -0400 Subject: [PATCH 46/50] Added Decoration.NEW class --- clearpath_config/platform/decorations.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/clearpath_config/platform/decorations.py b/clearpath_config/platform/decorations.py index 11acbe7..c0a896a 100644 --- a/clearpath_config/platform/decorations.py +++ b/clearpath_config/platform/decorations.py @@ -106,3 +106,22 @@ def __init__( model: str = DEFAULT ) -> None: super().__init__(name, enabled, model) + + +class Decoration(): + BUMPER = Bumper.DECORATION_MODEL + TOP_PLATE = TopPlate.DECORATION_MODEL + + MODEL = { + BUMPER: Bumper, + TOP_PLATE: TopPlate + } + + def __new__(cls, model: str) -> BaseDecoration: + assert model in Decoration.MODEL, ( + "Model '%s' must be one of: '%s'" % ( + model, + Decoration.MODEL.keys() + ) + ) + return Decoration.MODEL[model]() From 31a75d7eec74763fa2ad87c724d697628508c4bc Mon Sep 17 00:00:00 2001 From: Luis Camero Date: Tue, 4 Apr 2023 17:43:02 -0400 Subject: [PATCH 47/50] Added method to retrieve all decorations --- clearpath_config/platform/base.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/clearpath_config/platform/base.py b/clearpath_config/platform/base.py index 6c0c79f..30c54ad 100644 --- a/clearpath_config/platform/base.py +++ b/clearpath_config/platform/base.py @@ -1,5 +1,5 @@ from clearpath_config.common import ListConfig, Platform -from clearpath_config.platform.decorations import Bumper, TopPlate +from clearpath_config.platform.decorations import BaseDecoration, Bumper, TopPlate from typing import List @@ -27,6 +27,13 @@ def __init__(self, model) -> None: TopPlate, str]( uid=ListConfig.uid_name) + # Decorations: Get All + def get_all_decorations(self) -> List[BaseDecoration]: + decorations = [] + decorations.extend(self.get_bumpers()) + decorations.extend(self.get_top_plates()) + return decorations + # Bumper: Add def add_bumper( self, From de1600ed56a8e6d5e8f44ac09a37a0d072b7f09a Mon Sep 17 00:00:00 2001 From: Luis Camero Date: Tue, 4 Apr 2023 17:48:55 -0400 Subject: [PATCH 48/50] Added get_enabled --- clearpath_config/platform/decorations.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/clearpath_config/platform/decorations.py b/clearpath_config/platform/decorations.py index c0a896a..540bd76 100644 --- a/clearpath_config/platform/decorations.py +++ b/clearpath_config/platform/decorations.py @@ -26,6 +26,9 @@ def get_name(self) -> str: def set_name(self, name) -> None: self.name = name + def get_enabled(self) -> bool: + return self.enabled + def enable(self) -> None: self.enabled = True From f297ffc987a65f11af5b2a53f001cd3d412e599f Mon Sep 17 00:00:00 2001 From: Luis Camero Date: Tue, 4 Apr 2023 17:49:11 -0400 Subject: [PATCH 49/50] Updated A200 sample --- clearpath_config/sample/a200_config.yaml | 42 ++++++++---------------- 1 file changed, 13 insertions(+), 29 deletions(-) diff --git a/clearpath_config/sample/a200_config.yaml b/clearpath_config/sample/a200_config.yaml index 0017b95..8e95d0f 100644 --- a/clearpath_config/sample/a200_config.yaml +++ b/clearpath_config/sample/a200_config.yaml @@ -23,7 +23,7 @@ platform: # These are are parameters specific to a a platform extension: 0.1 model: wibotic rear_bumper: - extension: 0.2 + extension: 0 model: default top_plate: model: pacs @@ -31,54 +31,38 @@ platform: # These are are parameters specific to a a platform urdf: /fake/path/empty.urdf.xacro control: /fake/path/empty.yaml - mounts: fath_pivot: - - parent: "base_link" - angle: 0.0 + - parent: "top_plate_mount_a1" + angle: 1.5707 xyz: [0.0, 0.0, 0.0] rpy: [0.0, 0.0, 0.0] - - parent: "base_link" + - parent: "top_plate_mount_a2" angle: 0.0 xyz: [0.0, 0.0, 0.0] rpy: [0.0, 0.0, 0.0] - flir_ptu: - - parent: "base_link" - xyz: [0.0, 0.0, 0.0] - rpy: [0.0, 0.0, 0.0] - tty_port: "/dev/ptu" - tcp_port: 4000 - ip: "192.168.131.70" - connection_type: "tty" - limits_enabled: False - - parent: "base_link" - xyz: [0.0, 0.0, 0.0] - rpy: [0.0, 0.0, 0.0] - tty_port: "/dev/ptu" - tcp_port: 4000 - ip: "192.168.131.70" - connection_type: "tty" - limits_enabled: False riser: - rows: 8 columns: 7 + height: 0.1 thickness: 0.00635 - parent: "base_link" - xyz: [0.0, 0.0, 0.0] + parent: "top_plate_mount_a1" + xyz: [0.0, 0.0, 0.1] rpy: [0.0, 0.0, 0.0] - rows: 1 - columns: 1 + columns: 7 + height: 0.1 thickness: 0.00635 - parent: "base_link" - xyz: [0.0, 0.0, 0.0] + parent: "top_plate_mount_a6" + xyz: [0.0, 0.0, 0.2] rpy: [0.0, 0.0, 0.0] bracket: - model: "horizontal" - parent: "base_link" + parent: "riser_1_mount_g6" xyz: [0.0, 0.0, 0.0] rpy: [0.0, 0.0, 0.0] - model: "vertical" - parent: "base_link" + parent: "riser_1_mount_a6" xyz: [0.0, 0.0, 0.0] rpy: [0.0, 0.0, 0.0] From 2fdc66b1bd740ea979374e0c6e2ad9204f3da291 Mon Sep 17 00:00:00 2001 From: Luis Camero Date: Wed, 5 Apr 2023 10:17:19 -0400 Subject: [PATCH 50/50] Removed height from Husky sample --- clearpath_config/sample/a200_config.yaml | 2 -- 1 file changed, 2 deletions(-) diff --git a/clearpath_config/sample/a200_config.yaml b/clearpath_config/sample/a200_config.yaml index 8e95d0f..fef6d9a 100644 --- a/clearpath_config/sample/a200_config.yaml +++ b/clearpath_config/sample/a200_config.yaml @@ -44,14 +44,12 @@ mounts: riser: - rows: 8 columns: 7 - height: 0.1 thickness: 0.00635 parent: "top_plate_mount_a1" xyz: [0.0, 0.0, 0.1] rpy: [0.0, 0.0, 0.0] - rows: 1 columns: 7 - height: 0.1 thickness: 0.00635 parent: "top_plate_mount_a6" xyz: [0.0, 0.0, 0.2]