Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Iterables #7

Merged
merged 50 commits into from
Apr 5, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
50 commits
Select commit Hold shift + click to select a range
78e9ab3
Removed unecessary platform specific decorations
luis-camero Mar 28, 2023
f4e0dee
Added base decorations config
luis-camero Mar 28, 2023
c0d1346
Removed PACS specific configs
luis-camero Mar 28, 2023
d9d1fad
Added get and set functions to ListConfig
luis-camero Mar 28, 2023
99698ec
Fixed assertion indenting
luis-camero Mar 28, 2023
e530093
Removed unused imports
luis-camero Mar 28, 2023
5d5b36c
Fixed assertion indenting
luis-camero Mar 28, 2023
5902e23
Updated A200 to iterable decorations
luis-camero Mar 28, 2023
e54062c
Updated J100 to iterable decorations
luis-camero Mar 28, 2023
9e1f353
Updated path to config in Platform
luis-camero Mar 28, 2023
240487b
Updated path to base decorations config
luis-camero Mar 28, 2023
f2b5984
Switched parser to new decorations config
luis-camero Mar 28, 2023
6ccddb5
Removed PACS Config testers
luis-camero Mar 29, 2023
3f88d69
Fixed lint errors in clearpath_config
luis-camero Mar 16, 2023
3012a17
Fixed lint errors in mounts
luis-camero Mar 16, 2023
5c6aaa3
Moved ListConfig
luis-camero Mar 30, 2023
5422062
Updated remove function
luis-camero Mar 30, 2023
05ad4ec
Removed mount pseudo namespace
luis-camero Mar 30, 2023
75772fa
Small lint fixes in common
luis-camero Mar 30, 2023
1dda483
Added get and set methods for individual mounts
luis-camero Mar 30, 2023
fdd47da
Split up mounts
luis-camero Mar 30, 2023
21b9305
Added uid checks to ListConfig
luis-camero Apr 3, 2023
feb651e
Removed mounting link and model
luis-camero Apr 3, 2023
d04cab7
Removed mounting link from fath and flir moutns
luis-camero Apr 3, 2023
d6d69ef
Added OrderedListConfig
luis-camero Apr 3, 2023
830b7e9
Added name from id to BaseMount
luis-camero Apr 3, 2023
fd346fe
Removed name as a default parameter
luis-camero Apr 3, 2023
9098936
Removed PACS from platform
luis-camero Apr 3, 2023
7e07db6
Moved ListConfig and all PACS from the Platform base
luis-camero Apr 3, 2023
580e2f1
Added mounts as individual ordered lists
luis-camero Apr 3, 2023
d965a62
Updated sample to new mount iterables
luis-camero Apr 4, 2023
6c0428b
Clear OrderedConfigList if empty list is set
luis-camero Apr 4, 2023
e8224e8
BaseMount no longer requires a name, default to index
luis-camero Apr 4, 2023
d11336d
Removed 'pacs_' prefix from brackets and risers
luis-camero Apr 4, 2023
dd1141a
Completely disabled all PACS testing
luis-camero Apr 4, 2023
bd9f6ef
Removed name as required argument
luis-camero Apr 4, 2023
016d294
Upgraded parser to match new mounts
luis-camero Apr 4, 2023
03594b9
Fixed key error print statement
luis-camero Apr 4, 2023
1f3f85f
Removed and
luis-camero Apr 4, 2023
729ea1f
Updated Husky sampl
luis-camero Apr 4, 2023
a2e128f
Added __init__ to mounts
luis-camero Apr 4, 2023
7f8cf03
Added BaseDecoration; by default disabled
luis-camero Apr 4, 2023
b3c07c4
Fixed top plate in parser
luis-camero Apr 4, 2023
ec952d2
Set decorations to enabled if not specified but exist
luis-camero Apr 4, 2023
a42ed01
Added method to retrieve all mounts
luis-camero Apr 4, 2023
d3bb6bc
Added Decoration.NEW class
luis-camero Apr 4, 2023
31a75d7
Added method to retrieve all decorations
luis-camero Apr 4, 2023
de1600e
Added get_enabled
luis-camero Apr 4, 2023
f297ffc
Updated A200 sample
luis-camero Apr 4, 2023
2fdc66b
Removed height from Husky sample
luis-camero Apr 5, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion clearpath_config/clearpath_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
from clearpath_config.platform.platform import PlatformConfig
from clearpath_config.mounts.mounts import MountsConfig


# ClearpathConfig:
# - top level configurator
# - contains
Expand All @@ -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()
302 changes: 281 additions & 21 deletions clearpath_config/common.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from typing import List
from copy import deepcopy
from typing import Callable, Generic, List, TypeVar
import os
import re

Expand Down Expand Up @@ -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
Expand All @@ -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}(?<!-)$", re.IGNORECASE)
assert all(allowed.match(x) for x in hostname.split(".")), (
"Hostname '%s' cannot contain characters other than [A-Z][0-9] and hypens ('-')."
% hostname
"Hostname '%s' cannot contain characters other than %s." % (
hostname,
"[A-Z][0-9] and hypens ('-')"
)
)


Expand Down Expand Up @@ -115,16 +119,20 @@ def is_valid(ip: str) -> 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
Expand Down Expand Up @@ -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
Expand All @@ -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, (
Expand 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:
Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -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]:
Expand All @@ -275,19 +289,265 @@ 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: 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:
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

# 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())


# 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()
# If Empty Keep Empty
if not _list:
return
# Add One-by-One
try:
for obj in _list:
self.add(obj)
# Restore Save if Failure
except AssertionError:
self.__list = tmp_list
self.update()

# 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
Empty file.
Loading