diff --git a/pytest.ini b/pytest.ini index 68b0058..a24b764 100644 --- a/pytest.ini +++ b/pytest.ini @@ -1,7 +1,7 @@ [pytest] log_cli = True log_cli_date_format = %Y-%m-%d %H:%M:%S -log_cli_format = %(asctime)s %(levelname)-7s %(threadName)-18s %(name)-32s %(message)s +log_cli_format = %(asctime)s %(levelname)-8s %(threadName)-18s %(name)-32s [%(funcName)s-%(lineno)d] %(message)s log_cli_level = 60 ; log_file = logs/pytest-logs.txt diff --git a/roboglia/base/bus.py b/roboglia/base/bus.py index 277a606..eecc797 100644 --- a/roboglia/base/bus.py +++ b/roboglia/base/bus.py @@ -179,7 +179,7 @@ def __init__(self, name='FILEBUS', robot=None, port='', auto=True, **kwargs) self.__fp = None self.__last = {} - logger.debug(f'FileBus {self.name} initialized') + logger.debug(f'FileBus "{self.name}" initialized') def open(self): """Opens the file associated with the ``FileBus``.""" @@ -228,15 +228,16 @@ def write(self, reg, value): logger.error(f'attempt to write to closed bus {self.name}') else: self.__last[(reg.device.dev_id, reg.address)] = value - text = f'written {value} in register {reg.name} ' + \ - f'({reg.address}) of device {reg.device.dev_id}' + text = f'written {value} in register "{reg.name}"" ' + \ + f'({reg.address}) of device "{reg.device.name}" ' + \ + f'({reg.device.dev_id})' try: self.__fp.write(text + '\n') self.__fp.flush() except Exception: logger.error(f'error executing write and flush to file ' f'for bus: {self.name}') - logger.debug(f'FileBus {self.name} {text}') + logger.debug(f'FileBus "{self.name}" {text}') def read(self, reg): """Reads the value from the buffer of ``FileBus`` and logs it. @@ -272,15 +273,15 @@ def read(self, reg): if (reg.device.dev_id, reg.address) not in self.__last: self.__last[(reg.device.dev_id, reg.address)] = reg.default val = self.__last[(reg.device.dev_id, reg.address)] - text = f'read {val} from register {reg.name} ){reg.address}) ' + \ - f'of device {reg.device.dev_id}' + text = f'read {val} from register "{reg.name}" ({reg.address}) ' +\ + f'of device "{reg.device.name}" ({reg.device.dev_id})' try: - self.__fp.write(text + '\n') + self.__fp.write(text+'\n') self.__fp.flush() except Exception: logger.error(f'error executing write and flush to file ' f'for bus: {self.name}') - logger.debug(f'FileBus {self.name} {text}') + logger.debug(f'FileBus "{self.name}" {text}') return val def __str__(self): diff --git a/roboglia/base/joint.py b/roboglia/base/joint.py index e7dfe75..85d7357 100644 --- a/roboglia/base/joint.py +++ b/roboglia/base/joint.py @@ -15,6 +15,8 @@ import logging from statistics import mean +from math import nan, isnan, isclose + from ..utils import check_key, check_type, check_options, check_not_empty from .device import BaseDevice @@ -30,88 +32,50 @@ # So, we implemented with an old-fashioned class. class PVL(): """A representation of a (position, value, load) command that supports - ``None`` value components and implements a number of help functions + ``nan`` value components and implements a number of help functions like addition, substraction, negation, equality (with error margin) and representation. Parameters - ---------- - p: float or ``None`` + --------- + p: float or ``nan`` The position value of the PVL - v: float or ``None`` + v: float or ``nan`` The velocity value of the PVL - ld: float or ``None`` + ld: float or ``nan`` The load value of the PVL """ - def __init__(self, p=None, v=None, ld=None): + def __init__(self, p=nan, v=nan, ld=nan): self.__p = p self.__v = v self.__ld = ld @property def p(self): - """The position in PVL.""" + """The position value in PVL.""" return self.__p @property def v(self): - """The velocity in PVL.""" + """The velocity value in PVL.""" return self.__v @property def ld(self): - """The load in PVL.""" + """The load value in PVL.""" return self.__ld - def __neg1(self, value): - """Inverts one value that could be ``None``. ``None`` inverted is - ``None``. Numbers are inverted normally.""" - return None if value is None else -value - - def __add1(self, val1, val2): - """Adds two numbers that could be ``None``. ``None`` plus anything - is ``None``. Numbers are added normally.""" - return None if val1 is None or val2 is None else val1 + val2 - - def __diff1(self, val1, val2): - """Calculates difference between two numbers that could be ``None``. - It practically calculates the sum with the inverted ``val2`` for - convenience.""" - return self.__add1(val1, self.__neg1(val2)) - - def __eq1(self, val1, val2): - """Utility function: compares two values that could be ``None``. Two - ``None`` are equal, one ``None`` and one float are not. Floats are - equal if the absolute difference between them is less than 0.01. - - .. todo:: See if it is possible to make this threshold dynamic such - that the value for radians for instance have a different threshold - than one ones for degrees. One option would be to read the min - and max values of the component and determine from there. - - """ - if val1 is None and val2 is None: - return True - if val1 is None or val2 is None: - return False - return abs(val1 - val2) < 0.01 - def __eq__(self, other): """Comparison of two PVLs with margin of error. - Compare components of PVL one to one. ``Nones`` are the same if - both are ``None``. Numbers are the same if the absolute difference - between them is less than 0.01 (to account for small rounding errors + Compare components of PVL one to one. ``nan`` are the same if + both are ``nan``. Numbers are the same if the relative difference + between them is less than 0.1% (to account for small rounding errors that might result from conversion of values from external to internal format). - TODO: see if it is possible to make this threshold dynamic such that - the value for radians for instance have a different threshold - than one ones for degrees. One option would be to read the - min and max values of the component and determine from there. - Parameters ---------- other: PVL @@ -120,16 +84,22 @@ def __eq__(self, other): Returns ------- True: - if all components match (are ``None`` in the same place) or the + if all components match (are ``nan`` in the same place) or the differences are bellow the threshold False: if there are differences on any component of the PVLs. """ + def isclose_with_nan(val1, val2, rel_tol=1e-09, abs_tol=0.0): + if isnan(val1) and isnan(val2): + return True + else: + return isclose(val1, val2, rel_tol=rel_tol, abs_tol=abs_tol) + if isinstance(other, PVL): - return self.__eq1(self.p, other.p) and \ - self.__eq1(self.v, other.v) and \ - self.__eq1(self.ld, other.ld) + return isclose_with_nan(self.p, other.p, rel_tol=0.001) and \ + isclose_with_nan(self.v, other.v, rel_tol=0.001) and \ + isclose_with_nan(self.ld, other.ld, rel_tol=0.001) else: return False @@ -145,7 +115,7 @@ def __sub__(self, other): - a number (float or int) - a list of 3 numbers (float or int) - Substracting ``None`` with anything results in ``None``. Numbers + Substracting ``nan`` with anything results in ``nan``. Numbers are substracted normally. Returns @@ -154,17 +124,17 @@ def __sub__(self, other): The result as a PVL. """ if isinstance(other, PVL): - return PVL(p=self.__diff1(self.p, other.p), - v=self.__diff1(self.v, other.v), - ld=self.__diff1(self.ld, other.ld)) + return PVL(p=(self.p - other.p), + v=(self.v - other.v), + ld=(self.ld - other.ld)) elif isinstance(other, float) or isinstance(other, int): - return PVL(p=self.__diff1(self.p, other), - v=self.__diff1(self.v, other), - ld=self.__diff1(self.ld, other)) + return PVL(p=(self.p - other), + v=(self.v - other), + ld=(self.ld - other)) elif isinstance(other, list) and len(other) == 3: - return PVL(p=self.__diff1(self.p, other[0]), - v=self.__diff1(self.v, other[1]), - ld=self.__diff1(self.ld, other[2])) + return PVL(p=(self.p - other[0]), + v=(self.v - other[1]), + ld=(self.ld - other[2])) else: raise RuntimeError(f'Incompatible __sub__ paramters for {other}') @@ -180,7 +150,7 @@ def __add__(self, other): - a number (float or int) - a list of 3 numbers (float or int) - Adding ``None`` with anything results in ``None``. Numbers are + Adding ``nan`` with anything results in ``nan``. Numbers are added normally. Returns @@ -189,26 +159,26 @@ def __add__(self, other): The result as a PVL. """ if isinstance(other, PVL): - return PVL(p=self.__add1(self.p, other.p), - v=self.__add1(self.v, other.v), - ld=self.__add1(self.ld, other.ld)) + return PVL(p=(self.p + other.p), + v=(self.v + other.v), + ld=(self.ld + other.ld)) elif isinstance(other, float) or isinstance(other, int): - return PVL(p=self.__add1(self.p, other), - v=self.__add1(self.v, other), - ld=self.__add1(self.ld, other)) + return PVL(p=(self.p + other), + v=(self.v + other), + ld=(self.ld + other)) elif isinstance(other, list) and len(other) == 3: - return PVL(p=self.__add1(self.p, other[0]), - v=self.__add1(self.v, other[1]), - ld=self.__add1(self.ld, other[2])) + return PVL(p=(self.p + other[0]), + v=(self.v + other[1]), + ld=(self.ld + other[2])) else: raise RuntimeError(f'Incompatible __add__ paramters for {other}') def __neg__(self): - """Returns the inverse of a PVL. ``None`` values stay the same, floats + """Returns the inverse of a PVL. ``nan`` values stay the same, floats are negated.""" - return PVL(p=self.__neg1(self.p), - v=self.__neg1(self.v), - ld=self.__neg1(self.ld)) + return PVL(p=(-1 * self.p), + v=(-1 * self.v), + ld=(-1 * self.ld)) def __repr__(self): """Convenience representation of a PVL.""" @@ -219,31 +189,31 @@ class PVLList(): """A class that holds a list of PVL commands and provides a number of extra manipulation functions. - The constructor pads the supplied lists with ``None`` in case the + The constructor pads the supplied lists with ``nan`` in case the lists are unequal in size. Parameters ---------- - p: list of [float or ``None``] - The position commands as a list of float or ``None`` like this:: + p: list of [float or ``nan``] + The position commands as a list of float or ``nan`` like this:: - p=[1, 2, None, 30, None, 20, 10, None] + p=[1, 2, nan, 30, nan, 20, 10, nan] - v: list of [float or ``None``] - The velocity commands as a list of float or ``None`` + v: list of [float or ``nan``] + The velocity commands as a list of float or ``nan`` - ld: list of [float or ``None``] - The load commands as a list of float or ``None`` + ld: list of [float or ``nan``] + The load commands as a list of float or ``nan`` """ def __init__(self, p=[], v=[], ld=[]): length = max(len(p), len(v), len(ld)) # pads the short lists if len(p) < length: - p = p + [None] * (length - len(p)) + p = p + [nan] * (length - len(p)) if len(v) < length: - v = v + [None] * (length - len(v)) + v = v + [nan] * (length - len(v)) if len(ld) < length: - ld = ld + [None] * (length - len(ld)) + ld = ld + [nan] * (length - len(ld)) self.__items = [PVL(p[index], v[index], ld[index]) for index in range(length)] @@ -268,23 +238,23 @@ def __repr__(self): @property def positions(self): """Returns the full list of positions (p) commands, including - ``None`` from the list.""" + ``nan`` from the list.""" return [item.p for item in self.items] @property def velocities(self): """Returns the full list of velocities (v) commands, including - ``None`` from the list.""" + ``nan`` from the list.""" return [item.v for item in self.items] @property def loads(self): - """Returns the full list of load (ld) commands, including ``None`` + """Returns the full list of load (ld) commands, including ``nan`` from the list.""" return [item.ld for item in self.items] def append(self, - p=None, v=None, ld=None, + p=nan, v=nan, ld=nan, p_list=[], v_list=[], l_list=[], pvl=None, pvl_list=[]): @@ -306,7 +276,7 @@ def append(self, if p_list or v_list or l_list: new_pvl_list = PVLList(p_list, v_list, l_list) self.__items.extend(new_pvl_list.items) - if p is not None or v is not None or ld is not None: + if not isnan(p) or not isnan(v) or not isnan(ld): self.__items.append(PVL(p, v, ld)) def __process_one(self, attr, func): @@ -327,16 +297,16 @@ def __process_one(self, attr, func): Returns ------- float or None: - If the list contains non ``None`` values it will return the + If the list contains non ``nan`` values it will return the aggregation of them. To make things more efficient, if only - one non ``None`` value is identified, it is returned instead + one non ``nan`` value is identified, it is returned instead of applying the aggregation function. If no values are in the - list it returns ``None``. + list it returns ``nan``. """ items = [getattr(item, attr) for item in self.__items - if getattr(item, attr) is not None] + if not isnan(getattr(item, attr))] if len(items) == 0: - return None + return nan elif len(items) == 1: return items[0] else: @@ -366,7 +336,7 @@ def process(self, p_func=mean, v_func=mean, ld_func=mean): PVL: A PVL object with the aggregated result. If any of the components is missing any values in the list it will be reflected with - ``None`` value in that position. + ``nan`` value in that position. """ p = self.__process_one('p', p_func) v = self.__process_one('v', v_func) @@ -579,14 +549,14 @@ def value(self): """Generic accessor / setter that uses tuples to interact with the joint. For position only joints only position is set. """ - return PVL(self.position, None, None) + return PVL(self.position, nan, nan) @value.setter def value(self, pvl): """``values`` should be a tuple in all circumstances. For position only joints only position is used. """ - if pvl.p is not None: + if not isnan(pvl.p): self.position = pvl.p @property @@ -594,7 +564,7 @@ def desired(self): """Generic accessor for desired joint values. Always a tuple. For position only joints only position attribute is used. """ - return PVL(self.desired_position, None, None) + return PVL(self.desired_position, nan, nan) def __repr__(self): return f'{self.name}: p={self.position:.3f}' @@ -665,7 +635,7 @@ def desired_velocity(self): def value(self): """For a PV joint the value is a tuple with only 2 values used: (position, velocity).""" - return PVL(self.position, self.velocity, None) + return PVL(self.position, self.velocity, nan) @value.setter def value(self, pvl): @@ -675,16 +645,16 @@ def value(self, pvl): ---------- values: PVL (position, velocity, None) """ - if pvl.p is not None: + if not isnan(pvl.p): self.position = pvl.p - if pvl.v is not None: + if not isnan(pvl.v): self.velocity = pvl.v @property def desired(self): """For PV joint the desired is a tuple with only 2 values used. """ - return PVL(self.desired_position, self.desired_velocity, None) + return PVL(self.desired_position, self.desired_velocity, nan) def __repr__(self): return f'{Joint.__repr__(self)}, v={self.velocity:.3f}' @@ -767,11 +737,11 @@ def value(self, pvl): ---------- values: tuple (position, velocity, load) """ - if pvl.p is not None: + if not isnan(pvl.p): self.position = pvl.p - if pvl.v is not None: + if not isnan(pvl.v): self.velocity = pvl.v - if pvl.ld is not None: + if not isnan(pvl.ld): self.load = pvl.ld @property diff --git a/roboglia/base/robot.py b/roboglia/base/robot.py index 2ef0fd1..1a8d5b5 100644 --- a/roboglia/base/robot.py +++ b/roboglia/base/robot.py @@ -17,6 +17,7 @@ import logging import threading import statistics +import time from ..utils import get_registered_class, check_key, check_type, check_options from .thread import BaseLoop @@ -159,7 +160,7 @@ def __init_buses(self, buses): bus_class = get_registered_class(bus_info['class']) new_bus = bus_class(**bus_info) self.__buses[bus_name] = new_bus - logger.debug(f'bus {bus_name} added') + logger.debug(f'Bus "{bus_name}" added') def __init_devices(self, devices): """Called by ``__init__`` to parse and instantiate devices.""" @@ -188,7 +189,7 @@ def __init_devices(self, devices): new_dev = dev_class(**dev_info) self.__devices[dev_name] = new_dev self.__dev_by_id[dev_info['dev_id']] = new_dev - logger.debug(f'device {dev_name} added') + logger.debug(f'Device "{dev_name}" added') def __init_joints(self, joints): """Called by ``__init__`` to parse and instantiate joints.""" @@ -210,7 +211,7 @@ def __init_joints(self, joints): joint_class = get_registered_class(joint_info['class']) new_joint = joint_class(**joint_info) self.__joints[joint_name] = new_joint - logger.debug(f'joint {joint_name} added') + logger.debug(f'Joint "{joint_name}" added') def __init_sensors(self, sensors): """Called by ``__init__`` to parse and instantiate sensors.""" @@ -232,7 +233,7 @@ def __init_sensors(self, sensors): sensor_class = get_registered_class(sensor_info['class']) new_sensor = sensor_class(**sensor_info) self.__sensors[sensor_name] = new_sensor - logger.debug(f'sensor {sensor_name} added') + logger.debug(f'Sensor "{sensor_name}" added') def __init_groups(self, groups): """Called by ``__init__`` to parse and instantiate groups.""" @@ -256,7 +257,7 @@ def __init_groups(self, groups): logger, f'group {sub_grp_name} does not exist') new_grp.update(self.groups[sub_grp_name]) self.__groups[grp_name] = new_grp - logger.debug(f'group {grp_name} added') + logger.debug(f'Group "{grp_name}" added') def __init_syncs(self, syncs): """Called by ``__init__`` to parse and instantiate syncs.""" @@ -275,7 +276,7 @@ def __init_syncs(self, syncs): sync_class = get_registered_class(sync_info['class']) new_sync = sync_class(**sync_info) self.__syncs[sync_name] = new_sync - logger.debug(f'sync {sync_name} added') + logger.debug(f'Sync "{sync_name}" added') def __init_manager(self, manager): """Called by ``__init__`` to parse and instantiate the robot @@ -298,9 +299,10 @@ def __init_manager(self, manager): del manager['joints'] if 'group' in manager: del manager['group'] - self.__manager = JointManager(name=self.name, joints=joints, + name = manager.get('name', self.name+'-manager') + self.__manager = JointManager(name=name, joints=joints, group=group, **manager) - logger.debug(f'manager {self.name} added') + logger.debug(f'Manager "{self.manager.name}" added') @property def name(self): @@ -520,8 +522,8 @@ def __check_function(self, func_name, default=statistics.mean): 'max': max } if func_name not in supported: - logger.warning(f'Function {func_name} not supported. ' - f'Using {default}') + logger.info(f'Function {func_name} not supported. ' + f'Using {default}') return default else: return supported[func_name] @@ -668,9 +670,14 @@ def stop(self): """ # stop the streams logger.info('Stopping streams...') - while self.__streams: + start = time.time() + duration = 0 + while self.__streams and duration < 2.0: stream = list(self.__streams.values())[0] - stream.stop() + if stream.running: + stream.stop() + duration = time.time() - start + # while stream.running: # time.sleep(0.1) super().stop() @@ -689,8 +696,9 @@ def atomic(self): comm = self.__process_request(joint, self.__submissions) adj = self.__process_request(joint, self.__adjustments) value = comm + adj - logger.debug(f'Setting joint {joint.name}: value={value}') - joint.value = value + if not value == PVL(): # pragma: no branch + logger.debug(f'Setting joint {joint.name}: value={value}') + joint.value = value self.__lock.release() def __process_request(self, joint, requests): @@ -725,7 +733,7 @@ def __process_request(self, joint, requests): else: req.append(pvl=values) if len(req) == 0: - return PVL(None, None, None) + return PVL() # will be with ``nan``` if len(req) == 1: return req.items[0] else: diff --git a/roboglia/move/moves.py b/roboglia/move/moves.py index a09eee2..b9f4a62 100644 --- a/roboglia/move/moves.py +++ b/roboglia/move/moves.py @@ -88,8 +88,8 @@ def __init_scenes(self, scenes): for scene_name, scene_info in scenes.items(): sequences = scene_info.get('sequences', None) if not sequences: - logger.warning(f'Scene {scene_name} does not have any ' - f'sequences defined, it will be skipped') + logger.warning(f'Scene "{scene_name}" does not have any ' + f'sequences defined; will skip') self.__scenes[scene_name] = None else: # replace sequence names with object references @@ -107,9 +107,9 @@ def __init_scenes(self, scenes): # validate the sequence exists # if not log error and use None if seq_name not in self.sequences: - logger.warning(f'sequence {seq_name} used by scene ' - f'{scene_name} does not exist; ' - 'it will be skipped') + logger.warning(f'Sequence "{seq_name}" used by scene ' + f'"{scene_name}" does not exist; ' + 'will skip') seq['sequence'] = None else: seq['sequence'] = self.sequences[seq_name] @@ -123,8 +123,8 @@ def __init_script(self, script): """Called by __init__ to setup the script steps.""" for index, scene_name in enumerate(script): if scene_name not in self.scenes: - logger.warning(f'scene {scene_name} used by script ' - f'{self.name} does not exist - will be skipped') + logger.warning(f'Scene "{scene_name}" used by script ' + f'"{self.name}" does not exist; will skip') script[index] = None else: script[index] = self.scenes[scene_name] @@ -209,16 +209,17 @@ def times(self): def play(self): for step in range(self.times): - logger.debug(f'Scene {self.name} playing iteration {step+1}') + logger.debug(f'Scene "{self.name}" playing iteration {step+1}') for seq_ext in self.sequences: sequence = seq_ext['sequence'] reverse = seq_ext['reverse'] rev_text = ' in reverse' if reverse else '' if not sequence: - logger.debug('Skipping None sequence') + logger.debug(f'Scene "{self.name}" playing sequence ' + ' sequence - skipping') else: - logger.debug(f'Scene {self.name} playing sequence ' - f'{sequence.name}{rev_text}') + logger.debug(f'Scene "{self.name}" playing sequence ' + f'"{sequence.name}"{rev_text}') for frame, duration in sequence.play(reverse=reverse): yield frame, duration @@ -247,8 +248,8 @@ class Sequence(): def __init__(self, name='SEQUENCE', frames=[], durations=[], times=1): self.__name = name if len(frames) != len(durations): - logger.critical(f'durations supplied for sequence {name} do not ' - 'match the number of frames') + logger.warning(f'Durations for sequence "{name}" different than ' + 'the number of frames; will skip') return None else: self.__frames = frames @@ -292,25 +293,26 @@ def play(self, reverse=False): the frame. """ for step in range(self.times): - logger.debug(f'Sequence {self.name} playing iteration {step+1}') + logger.debug(f'Sequence "{self.name}" playing iteration {step+1}') if reverse: zipped = zip(reversed(self.frames), reversed(self.durations)) else: zipped = zip(self.frames, self.durations) for frame, duration in zipped: if frame: - logger.debug(f'Sequence {self.name} playing frame ' - f'{frame.name}, duration {duration}') + logger.debug(f'Sequence "{self.name}" playing frame ' + f'"{frame.name}", duration {duration}') yield frame.commands, duration else: - logger.debug('None frame - skipping') + logger.debug(f'Sequence "{self.name}" playing frame ' + ' frame - skipping') class Frame(): """A ``Frame`` is a single representation of the robots' joints at one point in time. It is described by a list of positions, the velocities wanted to get to those positions and the loads. The last two of them - are optional and will be padded with ``None`` in case they do not cover + are optional and will be padded with ``nan`` in case they do not cover all positions listed in the first parameter. Parameters @@ -327,25 +329,21 @@ class Frame(): velocities: list of floats The velocities used to move to the desired positions. If they are - empty or not all covered, the constructor will padded with ``None``s - to make it the same size as the positions. You can also use ``None`` + empty or not all covered, the constructor will padded with ``nan`` + to make it the same size as the positions. You can also use ``nan`` in the list to indicate that a particular joint does not need to change the velocity (will continue to use the one set previously). loads: list of floats The loads used to move to the desired positions. If they are - empty or not all covered, the constructor will padded with ``None``s - to make it the same size as the positions. You can also use ``None`` + empty or not all covered, the constructor will padded with ``nan`` + to make it the same size as the positions. You can also use ``nan`` in the list to indicate that a particular joint does not need to change the load (will continue to use the one set previously). """ def __init__(self, name='FRAME', positions=[], velocities=[], loads=[]): self.__name = name self.__pvl = PVLList(positions, velocities, loads) - # p_len = len(positions) - # self.__pos = positions - # self.__vel = velocities + [None] * max(0, p_len - len(velocities)) - # self.__loads = loads + [None] * max(0, p_len - len(loads)) @property def name(self): diff --git a/tests.py b/tests.py index 4130b70..6b1eab1 100644 --- a/tests.py +++ b/tests.py @@ -2,6 +2,7 @@ import logging import time import yaml +from math import nan from roboglia.utils import register_class, unregister_class, registered_classes, get_registered_class from roboglia.utils import check_key, check_options, check_type, check_not_empty @@ -1024,21 +1025,21 @@ def mock_robot(self): robot.stop() def test_pvl(self): - list1 = PVLList(p=[1,2,3], v=[None], ld=[None, 10, 10, None]) + list1 = PVLList(p=[1,2,3], v=[nan], ld=[nan, 10, 10, nan]) assert len(list1) == 4 - list1.append(p=10, v=20, ld=None) + list1.append(p=10, v=20, ld=nan) assert len(list1) == 5 - assert list1.positions == [1, 2, 3, None, 10] - assert list1.velocities == [None, None, None, None, 20] - assert list1.loads == [None, 10, 10, None, None] - list1.append(p_list=[20, None], v_list=[None, None, None], l_list=[]) + assert list1.positions == [1, 2, 3, nan, 10] + assert list1.velocities == [nan, nan, nan, nan, 20] + assert list1.loads == [nan, 10, 10, nan, nan] + list1.append(p_list=[20, nan], v_list=[nan, nan, nan], l_list=[]) assert len(list1) == 8 list2 = PVLList(p=[3,4,5], v=[1,1,1], ld=[5,5,5]) list1.append(pvl_list=list2) assert len(list1) == 11 list1.append(pvl=PVL(p=4, v=5, ld=6)) assert len(list1) == 12 - assert list1.positions == [1, 2, 3, None, 10, 20, None, None, 3, 4, 5, 4] + assert list1.positions == [1, 2, 3, nan, 10, 20, nan, nan, 3, 4, 5, 4] # func with one item list3 = PVLList() list3.append(pvl=PVL(10,10,10)) @@ -1046,15 +1047,15 @@ def test_pvl(self): assert avg == PVL(10,10,10) assert avg != 10 # adition - pvl1 = PVL(10, None, None) - assert pvl1 + 10 == PVL(20, None, None) - assert pvl1 - 10 == PVL(0, None, None) - assert pvl1 + PVL(5, 10, None) == PVL(15, None, None) - assert pvl1 - PVL(5, 10, None) == PVL(5, None, None) - assert pvl1 + [10, 20, 30] == PVL(20, None, None) - assert pvl1 - [10, 20, 30] == PVL(0, None, None) - assert -pvl1 == PVL(-10, None, None) - assert not pvl1 == PVL(None, None, None) + pvl1 = PVL(10, nan, nan) + assert pvl1 + 10 == PVL(20, nan, nan) + assert pvl1 - 10 == PVL(0, nan, nan) + assert pvl1 + PVL(5, 10, nan) == PVL(15, nan, nan) + assert pvl1 - PVL(5, 10, nan) == PVL(5, nan, nan) + assert pvl1 + [10, 20, 30] == PVL(20, nan, nan) + assert pvl1 - [10, 20, 30] == PVL(0, nan, nan) + assert -pvl1 == PVL(-10, nan, nan) + assert not pvl1 == PVL(nan, nan, nan) # raise errors with pytest.raises(RuntimeError): _ = pvl1 + [1,2] @@ -1076,11 +1077,11 @@ def test_move_load_robot(self, mock_robot): for joint in manager.joints: for comm in all_comm: joint.value = comm - assert mock_robot.joints['j01'].value == PVL(0,None,None) - assert mock_robot.joints['j02'].value == PVL(0,57.05,None) + assert mock_robot.joints['j01'].value == PVL(0,nan,nan) + assert mock_robot.joints['j02'].value == PVL(0,57.05,nan) assert mock_robot.joints['j03'].value == PVL(0,57.05,-50.0) - assert mock_robot.joints['j01'].desired == PVL(100, None, None) - assert mock_robot.joints['j02'].desired == PVL(100, 10.03, None) + assert mock_robot.joints['j01'].desired == PVL(100, nan, nan) + assert mock_robot.joints['j02'].desired == PVL(100, 10.03, nan) assert mock_robot.joints['j03'].desired == PVL(100, 10.03, 50.0) diff --git a/tests/moves/script_1.yml b/tests/moves/script_1.yml index 7d10372..d0152ce 100644 --- a/tests/moves/script_1.yml +++ b/tests/moves/script_1.yml @@ -12,8 +12,8 @@ script_1: frame_01: [100, 100, 100, 0] frame_02: [200, 200, 200, 0] frame_03: [400, 400, 400, 0] - frame_04: [null, null, 300, 0] - frame_05: [null, null, 100, 0] + frame_04: [nan, nan, 300, 0] + frame_05: [nan, nan, 100, 0] sequences: move_1: