From 4197aa2ba9aa7084c60bb4e665dbf318fb916a82 Mon Sep 17 00:00:00 2001 From: John Morris Date: Wed, 3 Feb 2021 12:15:29 -0600 Subject: [PATCH 01/32] .github: Fix included branch glob GH Actions should test branches whose name contain `/`. (PRs whose branches contain `/` are tested, though.) --- .github/workflows/ci.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 31ddfb6..9d43853 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -3,7 +3,7 @@ name: Build CI on: push: branches: - - '*' + - '**' pull_request: branches: - '*' From 5c92ccb88a12745ec195a9780f1ad228752f8d6a Mon Sep 17 00:00:00 2001 From: Robert Ellenberg Date: Tue, 29 Dec 2020 15:19:10 -0500 Subject: [PATCH 02/32] Use the first matching COMP_DIR instance when searching LD_LIBRARY_PATH --- hal_hw_interface/src/hal_hw_interface/hal_mgr.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/hal_hw_interface/src/hal_hw_interface/hal_mgr.py b/hal_hw_interface/src/hal_hw_interface/hal_mgr.py index ebc74c6..c84bc99 100644 --- a/hal_hw_interface/src/hal_hw_interface/hal_mgr.py +++ b/hal_hw_interface/src/hal_hw_interface/hal_mgr.py @@ -32,10 +32,13 @@ def __init__(self): def start(self): # Find the hal_hw_interface comp's directory in LD_LIBRARY_PATH and put it # into $COMP_DIR - comp_dir = "" for path in os.environ.get('LD_LIBRARY_PATH', '').split(':'): + rospy.loginfo(f"Checking for hal_hw_interface.so in {path}") if os.path.exists(os.path.join(path, 'hal_hw_interface.so')): comp_dir = path + break + else: + comp_dir = "" os.environ['COMP_DIR'] = comp_dir rospy.loginfo("hal_mgr: COMP_DIR set to '%s'" % comp_dir) From 2be7a5eb9170066b0178e8cb75298851e871ed61 Mon Sep 17 00:00:00 2001 From: "Robert W. Ellenberg" Date: Wed, 18 Nov 2020 23:18:13 -0500 Subject: [PATCH 03/32] Add __pycache__ to gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 674a035..2bad04d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ autosave.halscope +__pycache__ # bloom-generate rosdebian /hal_hw_interface/debian/ From 0f5b36c7792a0b7033ea8276eef2024c7e532534 Mon Sep 17 00:00:00 2001 From: "Robert W. Ellenberg" Date: Fri, 20 Nov 2020 17:27:59 -0500 Subject: [PATCH 04/32] hal_hw_interface: Don't hide GenericHWInterface init function This renames init to init_hal to better describe what it does. This should be safe to do because init_hal is called explicitly (not via virtual dispatch). --- hal_hw_interface/include/hal_hw_interface/hal_hw_interface.h | 2 +- hal_hw_interface/src/hal_control_loop.cpp | 2 +- hal_hw_interface/src/hal_hw_interface.cpp | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/hal_hw_interface/include/hal_hw_interface/hal_hw_interface.h b/hal_hw_interface/include/hal_hw_interface/hal_hw_interface.h index 47867f7..b3645ab 100644 --- a/hal_hw_interface/include/hal_hw_interface/hal_hw_interface.h +++ b/hal_hw_interface/include/hal_hw_interface/hal_hw_interface.h @@ -97,7 +97,7 @@ class HalHWInterface : public ros_control_boilerplate::GenericHWInterface */ //* \todo Give this an int return value for reporting failure //* \todo Make the `reset` pin an IO pin - void init(void (*funct)(void*, long)); + void init_hal(void (*funct)(void*, long)); /** * \brief Create float-type HAL pins for each joint diff --git a/hal_hw_interface/src/hal_control_loop.cpp b/hal_hw_interface/src/hal_control_loop.cpp index 55791ea..fc77211 100644 --- a/hal_hw_interface/src/hal_control_loop.cpp +++ b/hal_hw_interface/src/hal_control_loop.cpp @@ -74,7 +74,7 @@ HalRosControlLoop::HalRosControlLoop() : node_is_shutdown(0) CNAME); // Init HAL hardware interface - hardware_interface_->init(&funct); + hardware_interface_->init_hal(&funct); HAL_ROS_LOG_INFO(CNAME, "%s: Done initializing HAL hardware interface", CNAME); diff --git a/hal_hw_interface/src/hal_hw_interface.cpp b/hal_hw_interface/src/hal_hw_interface.cpp index b121934..e87b827 100644 --- a/hal_hw_interface/src/hal_hw_interface.cpp +++ b/hal_hw_interface/src/hal_hw_interface.cpp @@ -39,7 +39,7 @@ HalHWInterface::HalHWInterface(ros::NodeHandle& nh, urdf::Model* urdf_model) { } -void HalHWInterface::init(void (*funct)(void*, long)) +void HalHWInterface::init_hal(void (*funct)(void*, long)) { HAL_ROS_LOG_INFO(CNAME, "%s: Initializing HAL hardware interface", CNAME); From 08b1bf880de076f936eb10b3db8109d4262acaa4 Mon Sep 17 00:00:00 2001 From: John Morris Date: Sat, 30 Jan 2021 00:26:10 -0600 Subject: [PATCH 05/32] Make shutdown configurable with callbacks in python components --- hal_hw_interface/src/hal_hw_interface/hal_obj_base.py | 10 ++++++++++ .../src/hal_hw_interface/redis_store_hal_pin.py | 10 ---------- .../src/hal_hw_interface/ros_hal_component.py | 7 ++++--- 3 files changed, 14 insertions(+), 13 deletions(-) diff --git a/hal_hw_interface/src/hal_hw_interface/hal_obj_base.py b/hal_hw_interface/src/hal_hw_interface/hal_obj_base.py index f365351..ee01f0a 100644 --- a/hal_hw_interface/src/hal_hw_interface/hal_obj_base.py +++ b/hal_hw_interface/src/hal_hw_interface/hal_obj_base.py @@ -69,3 +69,13 @@ def get_ros_param(self, suffix, default=None): suffix, rospy.get_param('{}/{}'.format(self.compname, suffix), default), ) + + def add_shutdown_callback(self, cb): + """Add a shutdown callback + + Add a callback performing some extra shutdown action (such as + disconnect from a service) to a list that will be run from + :py:func:`hal_hw_interface.ros_hal_component.RosHalComponent.shutdown_component` + at component shut down time. + """ + self._cached_objs.setdefault('shutdown_cbs', []).append(cb) diff --git a/hal_hw_interface/src/hal_hw_interface/redis_store_hal_pin.py b/hal_hw_interface/src/hal_hw_interface/redis_store_hal_pin.py index 2159c73..6275e5a 100644 --- a/hal_hw_interface/src/hal_hw_interface/redis_store_hal_pin.py +++ b/hal_hw_interface/src/hal_hw_interface/redis_store_hal_pin.py @@ -82,13 +82,3 @@ def set_redis_from_pin(self): self._redis_config.get_param(self._redis_param_key), ) return new_val - - @classmethod - def shutdown(cls): - '''Closes the shared redis client connection. Call this from - :py:func:`hal_hw_interface.ros_hal_component.RosHalComponent.shutdown_component` - to close the connection at the HAL component's exit. - ''' - config = cls._cached_objs.get('redis_config_client', None) - if config is not None: - config.stop() diff --git a/hal_hw_interface/src/hal_hw_interface/ros_hal_component.py b/hal_hw_interface/src/hal_hw_interface/ros_hal_component.py index 1904d32..1aa9f07 100644 --- a/hal_hw_interface/src/hal_hw_interface/ros_hal_component.py +++ b/hal_hw_interface/src/hal_hw_interface/ros_hal_component.py @@ -132,10 +132,11 @@ def update(self): def shutdown_component(self): """Perform extra shutdown actions - This may optionally be defined in subclasses to take care of - extra shutdown actions, such as disconnecting from services. + Executes the list of callbacks defined by calls to + :py:func:`hal_hw_interface.hal_obj_base.HalObjBase.add_shutdown_callback`. """ - pass + for cb in self._cached_objs.setdefault('shutdown_cbs', []): + cb() def main(self): """The ROS node and HAL component `main()` function From 0ad15dfda909aa1e2b77714216c65d527738e937 Mon Sep 17 00:00:00 2001 From: John Morris Date: Sat, 30 Jan 2021 00:29:19 -0600 Subject: [PATCH 06/32] Remove duplicate rospy.get_param wrapper --- .../src/hal_hw_interface/ros_hal_component.py | 13 +------------ .../tests/test_ros_hal_component.py | 4 ++-- 2 files changed, 3 insertions(+), 14 deletions(-) diff --git a/hal_hw_interface/src/hal_hw_interface/ros_hal_component.py b/hal_hw_interface/src/hal_hw_interface/ros_hal_component.py index 1aa9f07..2b311c8 100644 --- a/hal_hw_interface/src/hal_hw_interface/ros_hal_component.py +++ b/hal_hw_interface/src/hal_hw_interface/ros_hal_component.py @@ -67,7 +67,7 @@ def __init__(self): rospy.loginfo("Initializing '%s' component" % self.compname) # Publisher update rate in Hz - self.update_rate = rospy.get_param('%s/update_rate' % self.compname, 10) + self.update_rate = self.get_ros_param('update_rate', 10) self.rate = rospy.Rate(self.update_rate) rospy.logdebug("Publish update rate = %.1f" % self.update_rate) @@ -81,17 +81,6 @@ def __init__(self): self.hal_comp.ready() rospy.loginfo("User component '%s' ready" % self.compname) - def get_param(self, key_suffix, default=None): - """Shorthand for `rospy.get_param(self.compname + key_suffix)` - - :param key_suffix: A suffix to append to :py:attr:`compname` - to form the full ROS parameter name - :type key_suffix: str - :param default: A default value, same as :py:func:`rospy.get_param` - """ - key = '{}/{}'.format(self.compname, key_suffix) - return rospy.get_param(key, default) - def setup_component(self): """Set up the ROS node and HAL component diff --git a/hal_hw_interface/src/hal_hw_interface/tests/test_ros_hal_component.py b/hal_hw_interface/src/hal_hw_interface/tests/test_ros_hal_component.py index cf19890..5caadcc 100644 --- a/hal_hw_interface/src/hal_hw_interface/tests/test_ros_hal_component.py +++ b/hal_hw_interface/src/hal_hw_interface/tests/test_ros_hal_component.py @@ -57,9 +57,9 @@ def test_ros_hal_component_init( def test_ros_hal_component_get_param(self, obj, mock_objs): '''Test get_param() ''' - res = obj.get_param('relative_tolerance', 42) + res = obj.get_ros_param('relative_tolerance', 42) assert res == 1e-9 - res = obj.get_param('bogus_key', 88) + res = obj.get_ros_param('bogus_key', 88) assert res == 88 def test_ros_hal_component_run(self, obj, mock_objs): From 2f530770ccfe99d5ffc095aae5a97a11af3af454 Mon Sep 17 00:00:00 2001 From: John Morris Date: Sat, 30 Jan 2021 00:38:09 -0600 Subject: [PATCH 07/32] hal_hw_interface: Rework RedisStoreHalPin `RedisStoreHalPin` is now more similar to the ROS topic and service pins. It has an `update()` function for copying input and IO pin values to redis, and a `redis_store` `ConfigClient` callback for writing redis updates to output and IO pins. Likewise, the `hal_io` component now may have redis pins. The `hal_offset_mgr` component has been removed; if it is ever revived, it will need minor rework for the new pin updates. --- .../src/hal_hw_interface/hal_io_comp.py | 10 + .../src/hal_hw_interface/hal_offset_mgr.py | 165 ----------- .../hal_hw_interface/redis_store_hal_pin.py | 107 +++++--- .../tests/test_hal_offset_mgr.py | 259 ------------------ 4 files changed, 72 insertions(+), 469 deletions(-) delete mode 100644 hal_hw_interface/src/hal_hw_interface/hal_offset_mgr.py delete mode 100644 hal_hw_interface/src/hal_hw_interface/tests/test_hal_offset_mgr.py diff --git a/hal_hw_interface/src/hal_hw_interface/hal_io_comp.py b/hal_hw_interface/src/hal_hw_interface/hal_io_comp.py index 9afc7ae..6abef4d 100644 --- a/hal_hw_interface/src/hal_hw_interface/hal_io_comp.py +++ b/hal_hw_interface/src/hal_hw_interface/hal_io_comp.py @@ -5,6 +5,7 @@ RosHalPinPublisher, RosHalPinService, ) +from hal_hw_interface.redis_store_hal_pin import RedisStoreHalPin class HalIO(RosHalComponent): @@ -26,10 +27,18 @@ class HalIO(RosHalComponent): enable: hal_type: BIT hal_dir: OUT + publish_pins: + digital_out_1: + hal_type: BIT + hal_dir: IN service_pins: encoder_scale: hal_type: FLOAT hal_dir: OUT + redis_pins: + current_tool: + hal_type: U32 + hal_dir: IO The periodic :py:func:`update` function calls the pins' :py:func:`update` functions, if any. @@ -45,6 +54,7 @@ def setup_component(self): subscribe_pins=RosHalPinSubscriber, publish_pins=RosHalPinPublisher, service_pins=RosHalPinService, + redis_pins=RedisStoreHalPin, ) for config_key, pin_class in pin_class_map.items(): pins = self.get_ros_param(config_key, dict()) diff --git a/hal_hw_interface/src/hal_hw_interface/hal_offset_mgr.py b/hal_hw_interface/src/hal_hw_interface/hal_offset_mgr.py deleted file mode 100644 index 8bade7b..0000000 --- a/hal_hw_interface/src/hal_hw_interface/hal_offset_mgr.py +++ /dev/null @@ -1,165 +0,0 @@ -# -*- coding: utf-8 -*- -import rospy -import attr -from hal_hw_interface.redis_store_hal_pin import RedisStoreHalPin -from hal_hw_interface.ros_hal_pin import RosHalPin -from hal_hw_interface.ros_hal_component import ( - RosHalComponent, - HalHWInterfaceException, -) - - -@attr.s -class HalOffsetMgrPin(RedisStoreHalPin): - '''Represents the offset of one joint. - - By default, the pin name is :code:`_offset`, where - :code:`` might simply be a joint name. This pin connects to - an :code:`offset` comp's :code:`offset` pin. Its value is - persisted in a redis store key - :code:`hal_offset_mgr/_offset`. - - A second, complementary :code:`_fb-in` input pin connects to - the :code:`offset` comp :code:`fb-in` pin. The - :code:`zero_joint()` function copies this value to the offset pin - and updates the redis store. - - :param name: The HAL pin name prefix - :type name: str - :param hal_type: HAL pin data type, one of :code:`['BIT', 'U32', - 'S32', 'FLOAT']` - :type hal_type: :py:class:`hal_hw_interface.hal_pin_attrs.HalPinType` - :param hal_dir: HAL pin direction, one of :code:`['IN', 'OUT', 'IO']` - :type hal_dir: :py:class:`hal_hw_interface.hal_pin_attrs.HalPinDir` - ''' - - _default_hal_type = 'FLOAT' - _default_hal_dir = 'OUT' - - offset_pin_name = attr.ib() - fb_in_pin_name = attr.ib() - - # Attribute default factories - @offset_pin_name.default - def _offset_pin_name_default(self): - return self.name + '_offset' - - @fb_in_pin_name.default - def _fb_in_pin_name_default(self): - return self.name + '_fb-in' - - @property - def pin_name(self): - return self.offset_pin_name - - def _hal_init(self): - # Set up the offset pin - super(HalOffsetMgrPin, self)._hal_init() - - # Set up fb-in pin - self.hal_fb_in_pin = RosHalPin(self.fb_in_pin_name, self.hal_type, 'IN') - - rospy.loginfo( - 'Created {} pin "{}" type "{}"'.format( - self.hal_dir, self.fb_in_pin_name, self.hal_type - ) - ) - - def zero_joint(self): - '''Zero the offset by copying the :code:`fb-in` pin to the - :code:`offset` pin and to redis - ''' - new_offset = self.hal_fb_in_pin.get_pin() - self.set_pin(new_offset) - self.set_redis_from_pin() - return new_offset - - def load_offset(self): - '''Load the offset value from redis and copy to the :code:`offset` pin - ''' - self.set_pin_from_redis(default=0) - value = self.get_pin() # Re-read pin for feedback - rospy.loginfo("Restored %s offset to %.4f", self.pin_name, value) - return value - - -class HalOffsetMgr(RosHalComponent): - '''HAL user component managing zero offsets that persist across - machine restarts - - It reads joint names from the ROS parameter server, and creates - two HAL pins for each: :code:`hal_offset_mgr._offset` and - :code:`hal_offset_mgr._fb-in`, meant to be attached to - corresponding pins of the joint's :code:`offset` component. - - The machine start-up procedure should pull the - :code:`hal_offset_mgr.load_params` command pin high. The - component will then copy saved offset values from the redis - :code:`hal_offset_mgr/_offset` keys to the corresponding - :code:`hal_offset_mgr._offset` pins for each joint, and pull - :code:`hal_offset_mgr.load_params` low again. - - The user interface should present the user with a 'zero offsets' - switch that requests all offsets zeroed at the current position by - pulling the :code:`hal_offset_mgr.zero_all_joints` pin high. The - :code:`hal_offset_mgr.enable` pin must be low, or the component - will refuse to do anything and issue an error message to that - effect. Otherwise, the component will zero offsets by copying the - :code:`hal_offset_mgr._fb-in` pins' values to the - :code:`hal_offset_mgr._offset` pins and the redis - :code:`hal_offset_mgr/_offset` keys for each joint. - ''' - - compname = 'hal_offset_mgr' - - def setup_component(self): - '''Read joints from ROS parameter server and create component pins - ''' - - # get joint info from hardware_interface config - joint_names = rospy.get_param('hardware_interface/joints', None) - if joint_names is None: - raise HalHWInterfaceException( - "Error reading ROS param hardware_interface/joints" - ) - - # create joint offset pins - self.offset_pins = [] - for name in joint_names: - o = HalOffsetMgrPin(name) - self.offset_pins.append(o) - - # create enable and zero + load command pins - self.zero_all_joints = RosHalPin('zero_all_joints', 'BIT', 'IO') - self.load_params = RosHalPin('load_params', 'BIT', 'IO') - self.enable = RosHalPin('enable', 'BIT', 'IN') - - def update(self): - '''Periodic update function; watches for and executes - :code:`load_params` or :code:`zero_all_joints` requests - ''' - - if self.load_params.get_pin(): - # Command: Load initial offsets from stored parameters - for p in self.offset_pins: - p.load_offset() - self.load_params.set_pin(False) - return - - if self.zero_all_joints.get_pin(): - # Command: Zero all joints - if self.enable.get_pin(): - rospy.logerr("Not zeroing joints while enabled") - self.zero_all_joints.set_pin(False) - return - - for p in self.offset_pins: - p.zero_joint() - - self.zero_all_joints.set_pin(False) - rospy.loginfo("All joints zeroed") - - def shutdown_component(self): - '''Close redis client connection at shutdown - ''' - RedisStoreHalPin.shutdown() diff --git a/hal_hw_interface/src/hal_hw_interface/redis_store_hal_pin.py b/hal_hw_interface/src/hal_hw_interface/redis_store_hal_pin.py index 6275e5a..8e61206 100644 --- a/hal_hw_interface/src/hal_hw_interface/redis_store_hal_pin.py +++ b/hal_hw_interface/src/hal_hw_interface/redis_store_hal_pin.py @@ -11,24 +11,28 @@ hal_hw_interface.redis_store_hal_pin.RedisStoreHalPin """ +import attr import rospy # Mock patching breaks with `from redis_store import ConfigClient`; why? import redis_store.config as redis_config -from hal_hw_interface.ros_hal_pin import RosHalPin +from hal_hw_interface.ros_hal_pin import RosHalPin, HalPinDir +@attr.s class RedisStoreHalPin(RosHalPin): '''HAL pin attached to :code:`redis_store` ROS package's parameter server This HAL pin's value may be read from and written to a - :code:`redis_store` parameter server. The default key name is - :code:`/`. + :code:`redis_store` parameter server. The default redis key name + is :code:`/`. :param name: The HAL pin name :type name: str + :param key: Set the redis key + :type key: str :param hal_type: HAL pin data type, one of :code:`['BIT', 'U32', 'S32', 'FLOAT']` :type hal_type: :py:class:`hal_hw_interface.hal_pin_attrs.HalPinType` @@ -36,49 +40,62 @@ class RedisStoreHalPin(RosHalPin): :type hal_dir: :py:class:`hal_hw_interface.hal_pin_attrs.HalPinDir` ''' - # Usually (?) read output pin default value from redis - _default_hal_dir = 'OUT' + key = attr.ib() - @property - def _redis_config(self): - if 'redis_config_client' not in self._cached_objs: - # Autovivify - c = redis_config.ConfigClient() - self._cached_objs['redis_config_client'] = c - return self._cached_objs['redis_config_client'] + # Attribute default factories + @key.default + def _key_default(self): + return '{}/{}'.format(self.compname, self.pin_name) + + redis_service_timeout_default = 20.0 # seconds to wait for service @property - def _redis_param_key(self): - return "{}/{}".format(self.compname, self.pin_name) - - def set_pin_from_redis(self, default=None): - '''Read and return value from redis_store - ''' - redis_value = self._redis_config.get_param(self._redis_param_key) - if redis_value is None and default is None: - rospy.logwarn( - "%s: Unable to initialize offset: " - "no saved value and no default given" % self.compname - ) - return - new_value = default if redis_value is None else redis_value - self.set_pin(new_value) - return new_value - - def set_redis_from_pin(self): - '''Read HAL pin value and write to redis_store - ''' - old_val = self._redis_config.get_param(self._redis_param_key) + def _redis_config(self): + if 'redis_config_client' in self._cached_objs: + return self._cached_objs['redis_config_client'] + + # Autovivify + timeout = self.get_ros_param( + 'redis_service_timeout', + self.redis_service_timeout_default + ) + rospy.loginfo(f"Connecting to redis database, timeout {timeout}s") + client = redis_config.ConfigClient(subscribe=True) + client.wait_for_service(timeout=timeout) + self._cached_objs['redis_config_client'] = client + # Disconnect from redis at shutdown + self.add_shutdown_callback(client.stop) + + return client + + def _ros_init(self): + if self.hal_dir == HalPinDir('IN'): + # Input pins write value out to redis + self._prev_pin_val = self.get_pin() + self._prev_redis_val = None + rospy.loginfo(f'Updating to redis key "{self.key}"') + else: + # Output pins read value from redis; IO pins go both ways + # but (arbitrarily) take starting value from redis. + val = self._redis_config.get_param(self.key) + self._prev_pin_val = self._prev_redis_val = val + if val is not None: # Key exists in redis + self.set_pin(val) + # Updates from redis are effected through a callback + self._redis_config.on_update_received.append(self._update_fm_redis) + rospy.loginfo(f'Updating from redis key "{self.key}"') + + def _update_fm_redis(self, key, value): + if key != self.key: + return # Not applicable + self.set_pin(value) + self._prev_pin_val = self._prev_redis_val = value + + def update(self): + """Write changed pin value to redis for input and IO pins + """ new_val = self.get_pin() - self._redis_config.set_param(self._redis_param_key, new_val) - if old_val is not None and not self._isclose( - new_val, old_val, 1e-5, 1e-5 - ): - rospy.loginfo( - "%s: Updated %s from %.4f to %.4f", - self.compname, - self._redis_param_key, - old_val, - self._redis_config.get_param(self._redis_param_key), - ) - return new_val + if self.hal_dir == HalPinDir('OUT') or self._prev_pin_val == new_val: + return # Not applicable + self._redis_config.set_param(self.key, new_val) + self._prev_pin_val = self._prev_redis_val = new_val diff --git a/hal_hw_interface/src/hal_hw_interface/tests/test_hal_offset_mgr.py b/hal_hw_interface/src/hal_hw_interface/tests/test_hal_offset_mgr.py deleted file mode 100644 index c1b797e..0000000 --- a/hal_hw_interface/src/hal_hw_interface/tests/test_hal_offset_mgr.py +++ /dev/null @@ -1,259 +0,0 @@ -# -*- coding: utf-8 -*- -import pytest - -from hal_hw_interface.ros_hal_pin import RosHalPin -from hal_hw_interface.hal_offset_mgr import HalOffsetMgrPin, HalOffsetMgr - -# Borrow tests from redis_store_hal_pin -from test_ros_hal_pin import HalPinDir, HalPinType -from test_redis_store_hal_pin import TestRedisStoreHalPin - - -class TestHalOffsetMgrPin(TestRedisStoreHalPin): - default_hal_type = HalPinType('FLOAT') - default_hal_dir = HalPinDir('OUT') - test_class = HalOffsetMgrPin - - newpin_calls = 2 - - def obj_test_name(self, obj): # Override base test - if isinstance(obj, self.test_class): - return self.get_obj_test_param(obj, 'name') + '_offset' - else: - return super(TestHalOffsetMgrPin, self).obj_test_name(obj) - - def test_ros_hal_pin_attrs(self, obj, mock_comp_obj): # Override base test - '''Test that attributes are set as expected, including defaults - ''' - # Call original test against offset pin - super(TestHalOffsetMgrPin, self).test_ros_hal_pin_attrs( - obj, mock_comp_obj - ) - - # Test complementary '_fb_in' newpin() call - pos_pin = obj.hal_fb_in_pin - assert pos_pin.name == obj.name + '_fb-in' - assert pos_pin.hal_comp is mock_comp_obj - assert type(pos_pin.hal_type) is HalPinType - assert pos_pin.hal_type == HalPinType(self.hal_type(obj)) - assert type(pos_pin.hal_dir) is HalPinDir - assert pos_pin.hal_dir == HalPinDir('IN') - - def test_ros_hal_pin_newpin(self, obj, mock_comp_obj): # Override base test - '''Test that hal.component.newpin() is called - ''' - # Call original test against offset pin - super(TestHalOffsetMgrPin, self).test_ros_hal_pin_newpin( - obj, mock_comp_obj - ) - - # Test complementary '_fb_in' newpin() call - mock_comp_obj.newpin.assert_any_call( - obj.name + '_fb-in', HalPinType(self.hal_type(obj)), HalPinDir('IN') - ) - - def test_hal_offset_mgr_pin_zero_joint( - self, obj, data, mock_comp_obj, mock_redis_client_obj - ): - '''Test zero_joint() sets offset pin and redis from position input pin - ''' - # Add obj test case data to position input pin object - pos_pin_case = obj._p.copy() - pos_pin_case['name'] = obj.name + '_fb-in' - obj.hal_fb_in_pin._p = pos_pin_case - - # Fake offset and position input values and run zero_joint() - new_val = self.set_pin(obj.hal_fb_in_pin, self.other_value(obj, data)) - old_val = self.set_pin(obj, data) - assert mock_comp_obj[obj.hal_fb_in_pin.name] == new_val # Sanity check - assert mock_comp_obj[obj.pin_name] == old_val # Sanity check - print("Calling zero_joint()") - updated_val = obj.zero_joint() - - # Check the pos pin was read correctly - assert updated_val == new_val - # Check the HAL pin value was written correctly - assert mock_comp_obj[self.obj_test_name(obj)] == new_val - # Check redis was written to - mock_redis_client_obj.set_param.assert_called_once_with( - '{}/{}'.format(self.compname, self.obj_test_name(obj)), new_val - ) - - def test_hal_offset_mgr_load_offset(self, obj, data): - # Add obj test case data to position input pin object - pos_pin_case = obj._p.copy() - pos_pin_case['name'] = obj.name + '_fb-in' - obj.hal_fb_in_pin._p = pos_pin_case - - # Fake redis and pin values and run load_offset() - redis_val = self.set_mock_redis_param(obj, data) - pin_val = self.set_pin(obj, data) - if data['changed']: # Sanity check - assert pin_val != redis_val - ret_val = obj.load_offset() - - # Check the HAL pin and return values are correct - assert obj.get_pin() == redis_val - assert ret_val == redis_val - - -class TestHalOffsetMgr(object): - test_class = HalOffsetMgr - test_pin_class = HalOffsetMgrPin - compname = 'hal_offset_mgr' - - joint_names = ['joint_1', 'joint_2'] - num_pins = len(joint_names) - offset_pins = ['{}_offset'.format(j) for j in joint_names] - fb_pins = ['{}_fb-in'.format(j) for j in joint_names] - redis_keys = ['%s/%s' % (compname, p) for p in offset_pins] - test_data = zip(offset_pins, fb_pins, redis_keys, (42, 13)) - - @pytest.fixture - def obj(self, mock_comp_obj, mock_rospy, mock_redis_client_obj, mock_objs): - for key in list(self.test_class._cached_objs.keys()): - self.test_class._cached_objs.pop(key) - - gp = mock_objs['rospy_get_param'] - gp.set_key('hardware_interface/joints', self.joint_names) - - mock_comp_obj.set_prefix(self.compname) - return self.test_class() - - def test_hal_offset_mgr_setup_component(self, obj): - # Check offset_pins list - assert len(obj.offset_pins) == len(self.joint_names) - for op in obj.offset_pins: - assert isinstance(op, self.test_pin_class) - assert op.hal_type == HalPinType('FLOAT') - assert op.hal_dir == HalPinDir('OUT') - - # Check other pins - for data in ( - ('zero_all_joints', 'IO'), - ('load_params', 'IO'), - ('enable', 'IN'), - ): - name, hal_dir = data - assert hasattr(obj, name) - pin = getattr(obj, name) - assert isinstance(pin, RosHalPin) - assert pin.hal_type == HalPinType('BIT') - assert pin.hal_dir == HalPinDir(hal_dir) - - def test_hal_offset_mgr_update_no_cmd(self, obj): - # Test no command - - obj.hal_comp.set_pin('load_params', 0) - obj.hal_comp.set_pin('zero_all_joints', 0) - obj.hal_comp.set_pin('enable', 0) - obj.hal_comp.reset_mock() - obj.update() - print(obj.hal_comp.mock_calls) - - # Cmd pins read, but nothing set - assert obj.hal_comp.__getitem__.call_count == 2 - obj.hal_comp.__getitem__.assert_any_call('load_params') - obj.hal_comp.__getitem__.assert_any_call('zero_all_joints') - obj.hal_comp.__setitem__.assert_not_called() - - def test_hal_offset_mgr_update_load_params( - self, obj, mock_redis_client_obj, mock_comp_obj - ): - # Test load_params command - - mock_comp_obj.setprefix(self.compname) - obj.hal_comp.set_pin('load_params', 1) - obj.hal_comp.set_pin('zero_all_joints', 0) - obj.hal_comp.set_pin('enable', 0) - for pin_o, pin_fb, redis_key, val in self.test_data: - mock_redis_client_obj.set_key(redis_key, val) - obj.hal_comp.reset_mock() - obj.update() - print("obj.hal_comp calls:") - print(obj.hal_comp.mock_calls) - print("mock_redis_client calls:") - print(mock_redis_client_obj.mock_calls) - - # total n+1 pin writes, n param reads, no param writes - assert obj.hal_comp.__setitem__.call_count == self.num_pins + 1 - assert mock_redis_client_obj.get_param.call_count == self.num_pins - mock_redis_client_obj.set_param.assert_not_called - # load_params read & written - obj.hal_comp.__getitem__.assert_any_call('load_params') - obj.hal_comp.__setitem__.assert_any_call('load_params', False) - # offsets written - for pin_o, pin_fb, redis_key, val in self.test_data: - mock_redis_client_obj.get_param.assert_any_call(redis_key) - assert mock_redis_client_obj.get_param(redis_key) == val - obj.hal_comp.__setitem__.assert_any_call(pin_o, val) - - def test_hal_offset_mgr_update_zero_all_joints_disabled( - self, obj, mock_redis_client_obj, mock_comp_obj - ): - # Test zero_all_joints command when disabled - - mock_comp_obj.setprefix(self.compname) - obj.hal_comp.set_pin('load_params', 0) - obj.hal_comp.set_pin('zero_all_joints', 1) - obj.hal_comp.set_pin('enable', 0) - for pin_o, pin_fb, redis_key, val in self.test_data: - obj.hal_comp.set_pin(pin_fb, val) - obj.hal_comp.reset_mock() - obj.update() - print("obj.hal_comp calls:") - print(obj.hal_comp.mock_calls) - print("mock_redis_client calls:") - print(mock_redis_client_obj.mock_calls) - - # total n+1 pin writes, n params read, 0 params written - assert obj.hal_comp.__setitem__.call_count == self.num_pins + 1 - assert mock_redis_client_obj.set_param.call_count == 2 - mock_redis_client_obj.set_param.assert_not_called - # zero_all_joints pin read & written - obj.hal_comp.__getitem__.assert_any_call('zero_all_joints') - obj.hal_comp.__setitem__.assert_any_call('zero_all_joints', False) - # enable read - obj.hal_comp.__getitem__.assert_any_call('enable') - # offsets read from fb pins, written to offset pins and redis - for pin_o, pin_fb, redis_key, val in self.test_data: - obj.hal_comp.__getitem__.assert_any_call(pin_fb) - obj.hal_comp.__setitem__.assert_any_call(pin_o, val) - assert obj.hal_comp[pin_o] == val - mock_redis_client_obj.set_param.assert_any_call(redis_key, val) - assert mock_redis_client_obj.get_param(redis_key) == val - - def test_hal_offset_mgr_update_zero_all_joints_enabled( - self, obj, mock_redis_client_obj - ): - # Test zero_all_joints command when enabled - - obj.hal_comp.set_pin('load_params', 0) - obj.hal_comp.set_pin('zero_all_joints', 1) - obj.hal_comp.set_pin('enable', 1) - obj.hal_comp.reset_mock() - obj.update() - print("obj.hal_comp calls:") - print(obj.hal_comp.mock_calls) - print("mock_redis_client calls:") - print(mock_redis_client_obj.mock_calls) - - # total 1 pin write - assert obj.hal_comp.__setitem__.call_count == 1 - mock_redis_client_obj.set_param.assert_not_called - mock_redis_client_obj.set_param.assert_not_called - # zero_all_joints pin read & written - obj.hal_comp.__getitem__.assert_any_call('zero_all_joints') - obj.hal_comp.__setitem__.assert_any_call('zero_all_joints', False) - # enable read - obj.hal_comp.__getitem__.assert_any_call('enable') - - def test_hal_offset_mgr_shutdown_component( - self, obj, mock_redis_client_obj - ): - # Test that redis client disconnects - obj.offset_pins[0]._redis_config # Autovivify client - obj.shutdown_component() - print("mock_redis_client calls:") - print(mock_redis_client_obj.mock_calls) - assert mock_redis_client_obj.stop.call_count == 1 From ed8cef93b7c4ac0107549fc4d7f71ef35d75acda Mon Sep 17 00:00:00 2001 From: John Morris Date: Tue, 18 May 2021 09:23:26 -0500 Subject: [PATCH 08/32] ros_hal_pin.py: Fix publisher The pin publisher fails to publish the last change occurring in a quick succession, instead latching a stale value. This change fixes that by comparing the pin value with the currently latched value rather than the locally-stored version that was going stale. --- .../src/hal_hw_interface/ros_hal_pin.py | 57 +++++++++++-------- 1 file changed, 32 insertions(+), 25 deletions(-) diff --git a/hal_hw_interface/src/hal_hw_interface/ros_hal_pin.py b/hal_hw_interface/src/hal_hw_interface/ros_hal_pin.py index 17ca007..1b82503 100644 --- a/hal_hw_interface/src/hal_hw_interface/ros_hal_pin.py +++ b/hal_hw_interface/src/hal_hw_interface/ros_hal_pin.py @@ -166,24 +166,26 @@ class RosHalPinPublisher(RosHalPin): _default_hal_dir = 'IN' pub_topic = attr.ib() msg_type = attr.ib() - last_value = attr.ib(default=None) # Attribute default factories @pub_topic.default def _pub_topic_default(self): return '{}/{}'.format(self.compname, self.pin_name) + _pin_to_msg_type_map = { + HalPinType('BIT'): Bool, + HalPinType('U32'): UInt32, + HalPinType('S32'): Int32, + HalPinType('FLOAT'): Float64, + } + @msg_type.default def _msg_type_default(self): - return { - HalPinType('BIT'): Bool, - HalPinType('U32'): UInt32, - HalPinType('S32'): Int32, - HalPinType('FLOAT'): Float64, - }[self.hal_type] + return self._pin_to_msg_type_map[self.hal_type] def _ros_init(self): self._ros_publisher_init() + self._msg = self._pin_to_msg_type_map[self.hal_type](self.get_pin()) def _ros_publisher_init(self): rospy.loginfo('Creating publisher on topic "{}"'.format(self.pub_topic)) @@ -193,26 +195,27 @@ def _ros_publisher_init(self): def _value_changed(self, value): if self.hal_type == HalPinType('FLOAT'): - changed = self.last_value is None or not self._isclose( - self.last_value, + changed = not self._isclose( + self._msg.data, value, rel_tol=self.get_ros_param('relative_tolerance', 1e-9), abs_tol=self.get_ros_param('absolute_tolerance', 1e-9), ) else: - changed = self.last_value != value + changed = self._msg.data != value return changed def update(self): """If pin value has changed, publish to ROS topic """ - # rospy.logdebug( - # "publish_pins: Publishing pin '%s' value '%s'" % - # (self.pin_name, self.get_pin())) value = self.get_pin() if self._value_changed(value): - self.last_value = value - self.pub.publish(value) + rospy.logdebug( + "publish_pins: Publishing pin '%s' value '%s'" % + (self.pin_name, self.get_pin())) + rospy.loginfo(f'Pin {self.pin_name} changed: old={self._msg.data}; new={value}') + self._msg.data = value + self.pub.publish(self._msg) @attr.s @@ -249,7 +252,7 @@ def _sub_topic_default(self): return '{}/{}'.format(self.compname, self.pin_name) def _ros_init(self): - self._ros_publisher_init() + super()._ros_init() self._ros_subscriber_init() def _ros_subscriber_init(self): @@ -269,7 +272,10 @@ def _subscriber_cb(self, msg): % (type(msg), self.pin_name, self.msg_type) ) - self.set_pin(msg.data) + if self._value_changed(msg.data): + rospy.loginfo(f'Pin {self.pin_name} subscriber change: old={self._msg.data}; new={msg.data}') + self.set_pin(msg.data) + self.update() @attr.s @@ -309,19 +315,20 @@ class RosHalPinService(RosHalPinPublisher): def _service_name_default(self): return '{}/{}'.format(self.compname, self.pin_name) + _pin_to_service_msg_type_map = { + HalPinType('BIT'): SetBool, + HalPinType('U32'): SetUInt32, + HalPinType('S32'): SetInt32, + HalPinType('FLOAT'): SetFloat64, + } + @service_msg_type.default def _service_msg_type_default(self): - return { - HalPinType('BIT'): SetBool, - HalPinType('U32'): SetUInt32, - HalPinType('S32'): SetInt32, - HalPinType('FLOAT'): SetFloat64, - }[self.hal_type] + return self._pin_to_service_msg_type_map[self.hal_type] def _ros_init(self): + super()._ros_init() self._ros_service_init() - # Publish the value on a topic, too - self._ros_publisher_init() def _ros_service_init(self): self.service = rospy.Service( From b9e10b67fe98a33843a2f332367763b2645eb982 Mon Sep 17 00:00:00 2001 From: John Morris Date: Tue, 18 May 2021 12:25:25 -0500 Subject: [PATCH 09/32] .pre-commit-config: Tweaks & update - Replace deprecated `check-byte-order-marker` - Remove unwanted `fix-encoding-pragma` - Remove unneeded QML checks - Run `pre-commit autoupdate` --- .pre-commit-config.yaml | 21 ++------------------- 1 file changed, 2 insertions(+), 19 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 8006ba8..37c4e2f 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,11 +1,10 @@ repos: - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v2.1.0 + rev: v4.0.1 hooks: - id: check-ast - - id: check-byte-order-marker + - id: fix-byte-order-marker - id: trailing-whitespace - exclude: '.*\.patch' - id: check-docstring-first - id: check-executables-have-shebangs - id: check-json @@ -14,7 +13,6 @@ repos: - id: check-xml - id: check-yaml - id: end-of-file-fixer - - id: fix-encoding-pragma - repo: local hooks: @@ -35,21 +33,6 @@ repos: types: [file, c++] # note: formatting style in .clang-format - - id: qmllint - name: Run qmllint - description: QML linter - entry: qmllint - language: system - files: .*\.qml - - - id: qmlfmt - name: Run qmlfmt - description: qmlfmt QML code formatter. - entry: qmlfmt -w -e - args: [-i 2, -t 2] - language: system - files: .*\.qml - - id: shfmt name: Run shfmt description: shfmt Shell code formatter. From f42f02e4fa5cbec10d092d41af872ff729b75884 Mon Sep 17 00:00:00 2001 From: John Morris Date: Tue, 18 May 2021 12:36:10 -0500 Subject: [PATCH 10/32] Run auto-formatters & fix problems --- .clang-format | 4 +- .github/docker/script_pre.sh | 22 +++---- .github/local-env-sample.yaml | 5 +- .travis.build-docs.sh | 37 ++++++----- .travis.setup-docker-image.sh | 2 +- hal_hw_interface/doc/conf.py | 2 +- .../hal_hw_interface/hal_control_loop.h | 32 +++++----- .../hal_hw_interface/hal_hw_interface.h | 64 +++++++++---------- hal_hw_interface/src/hal_control_loop.cpp | 2 +- hal_hw_interface/src/hal_hw_interface.cpp | 17 +++-- .../src/hal_hw_interface/hal_io_comp.py | 6 +- .../src/hal_hw_interface/hal_obj_base.py | 4 +- .../src/hal_hw_interface/hal_pin_attrs.py | 7 +- .../hal_hw_interface/redis_store_hal_pin.py | 12 ++-- .../src/hal_hw_interface/ros_hal_pin.py | 41 ++++++------ .../tests/test_redis_store_hal_pin.py | 9 ++- .../tests/test_ros_hal_component.py | 10 ++- .../tests/test_ros_hal_pin.py | 33 ++++------ 18 files changed, 145 insertions(+), 164 deletions(-) diff --git a/.clang-format b/.clang-format index 1f99de9..c0e3e0b 100644 --- a/.clang-format +++ b/.clang-format @@ -8,7 +8,6 @@ AllowAllParametersOfDeclarationOnNextLine: false AllowShortIfStatementsOnASingleLine: false AllowShortLoopsOnASingleLine: false AllowShortFunctionsOnASingleLine: None -AllowShortLoopsOnASingleLine: false AlwaysBreakTemplateDeclarations: true AlwaysBreakBeforeMultilineStrings: false BreakBeforeBinaryOperators: false @@ -51,7 +50,7 @@ SpaceAfterCStyleCast: false BreakBeforeBraces: Custom # Control of individual brace wrapping cases -BraceWrapping: { +BraceWrapping: AfterClass: 'true' AfterControlStatement: 'true' AfterEnum : 'true' @@ -62,5 +61,4 @@ BraceWrapping: { BeforeCatch : 'true' BeforeElse : 'true' IndentBraces : 'false' -} ... diff --git a/.github/docker/script_pre.sh b/.github/docker/script_pre.sh index 45ae5fa..c810853 100755 --- a/.github/docker/script_pre.sh +++ b/.github/docker/script_pre.sh @@ -5,22 +5,22 @@ ROS_DISTRO=noetic # Add package repos needed to build linuxcnc-ethercat # - IgH EtherLab Master curl -1sLf \ - 'https://dl.cloudsmith.io/public/zultron/etherlabmaster/cfg/setup/bash.deb.sh' \ - | bash + 'https://dl.cloudsmith.io/public/zultron/etherlabmaster/cfg/setup/bash.deb.sh' | + bash # - redis_store curl -1sLf \ - 'https://dl.cloudsmith.io/public/zultron/hal_ros_control/cfg/setup/bash.deb.sh' \ - | bash + 'https://dl.cloudsmith.io/public/zultron/hal_ros_control/cfg/setup/bash.deb.sh' | + bash # - Machinekit curl -1sLf \ - 'https://dl.cloudsmith.io/public/machinekit/machinekit-hal/cfg/setup/bash.deb.sh' \ - | bash + 'https://dl.cloudsmith.io/public/machinekit/machinekit-hal/cfg/setup/bash.deb.sh' | + bash # Bootstrap ROS installation # http://wiki.ros.org/noetic/Installation/Source echo "deb http://packages.ros.org/ros/ubuntu $(lsb_release -sc) main" \ - > /etc/apt/sources.list.d/ros-latest.list + >/etc/apt/sources.list.d/ros-latest.list apt-key adv --keyserver 'hkp://keyserver.ubuntu.com:80' \ --recv-key C1CF6E31E6BADE8868B172B4F42ED6FBAB17C654 apt-get update @@ -28,14 +28,14 @@ apt-get update pip3 install -U \ vcstool \ rosdep \ - rosinstall-generator \ + rosinstall-generator apt-get install -y \ build-essential # Custom rosdep keys mkdir -p /etc/ros/rosdep/sources.list.d -cat > /etc/ros/rosdep/machinekit-rosdep.yaml </etc/ros/rosdep/machinekit-rosdep.yaml < /etc/ros/rosdep/sources.list.d/10-local.list </etc/ros/rosdep/sources.list.d/10-local.list < my_ghid -GH_REPO_NAME=${GH_REPO_NAME:-${TRAVIS_REPO_SLUG#*/}} # my_ghid/my_repo -> my_repo +GH_REPO_ID=${GH_REPO_ID:-${TRAVIS_REPO_SLUG#/*}} # my_ghid/my_repo -> my_ghid +GH_REPO_NAME=${GH_REPO_NAME:-${TRAVIS_REPO_SLUG#*/}} # my_ghid/my_repo -> my_repo GH_REPO_URI=${GH_REPO_URI:-github.com/$TRAVIS_REPO_SLUG} GH_REPO_PULL_URL=${GH_REPO_PULL_URL:-https://${GH_REPO_URI}.git} GH_REPO_PUSH_URL=${GH_REPO_PUSH_URL:-https://${GH_REPO_TOKEN}@${GH_REPO_URI}} @@ -165,7 +164,7 @@ for subdir in ${DOC_SUBDIRS}; do echo "Running rosdoc_lite for $subdir" >&2 rosdoc_lite . 2>&1 | tee doc/rosdoc.log cd - - cp -a src/${GH_REPO_NAME}/$subdir/doc/html/* doc/ + cp -a src/${GH_REPO_NAME}/$subdir/doc/html/* doc/ done ################################################################################ @@ -194,7 +193,7 @@ if [ -f "index.html" ]; then # Force push to the remote gh-pages branch. # The ouput is redirected to /dev/null to hide any sensitive credential data # that might otherwise be exposed. - git push --force "${GH_REPO_PUSH_URL}" > /dev/null 2>&1 + git push --force "${GH_REPO_PUSH_URL}" >/dev/null 2>&1 else echo 'Not pushing docs with no $GH_REPO_TOKEN set' >&2 fi diff --git a/.travis.setup-docker-image.sh b/.travis.setup-docker-image.sh index cc2a4c2..1ecd4fe 100755 --- a/.travis.setup-docker-image.sh +++ b/.travis.setup-docker-image.sh @@ -8,7 +8,7 @@ fi if test -n "${DOCKER_REPO_AUTH}"; then echo "Setting up docker repo authentication" >&2 mkdir -p ~/.docker - cat > ~/.docker/config.json <~/.docker/config.json <.pos-cmd`, `.vel-cmd` and `.eff-cmd` -* * Input pins connecting joint feedback from HAL back to ROS -* * `.pos-fb`, `.vel-fb` and `.eff-fb` -* -* The `read()` function reads joint feedback values from the `.*-fb` HAL -* pins into the `hardware_interface::JointHandle`, and the `write()` function -* writes joint command values back out to the `.*-cmd` HAL pins. -* -* This is plumbed into a ROS node in the `hal_hw_interface::HalRosControlLoop` -* class. -* -* [1]: https://github.com/PickNikRobotics/ros_control_boilerplate -*/ + * \brief A `ros_control_boilerplate::GenericHWInterface` subclass for + * Machinekit HAL + * + * The `hal_hw_interface::HalHWInterface` class implements the Machinekit HAL + * realtime component: + * 1. Initializes the component + * 2. Implements the ros_control `read()` and `write()` functions + * 3. Shuts down the component + * + * The HAL component name is `hw_hw_interface`, and has one `reset` pin and six + * pins for each joint. + * + * The `reset` pin resets the ROS controllers whenever it is high. + * + * Joint names are read from configuration in [`ros_control_boilerplate`][1]. + * Six HAL pins are created for each joint: + * + * * Output pins connecting joint command from ROS into HAL + * * `.pos-cmd`, `.vel-cmd` and `.eff-cmd` + * * Input pins connecting joint feedback from HAL back to ROS + * * `.pos-fb`, `.vel-fb` and `.eff-fb` + * + * The `read()` function reads joint feedback values from the `.*-fb` HAL + * pins into the `hardware_interface::JointHandle`, and the `write()` function + * writes joint command values back out to the `.*-cmd` HAL pins. + * + * This is plumbed into a ROS node in the `hal_hw_interface::HalRosControlLoop` + * class. + * + * [1]: https://github.com/PickNikRobotics/ros_control_boilerplate + */ class HalHWInterface : public ros_control_boilerplate::GenericHWInterface { @@ -179,6 +179,6 @@ class HalHWInterface : public ros_control_boilerplate::GenericHWInterface }; // HalHWInterface -} // hardware_interface +} // namespace hal_hw_interface #endif // HAL_HW_INTERFACE_HAL_HW_INTERFACE_H diff --git a/hal_hw_interface/src/hal_control_loop.cpp b/hal_hw_interface/src/hal_control_loop.cpp index fc77211..f7eb6c8 100644 --- a/hal_hw_interface/src/hal_control_loop.cpp +++ b/hal_hw_interface/src/hal_control_loop.cpp @@ -139,7 +139,7 @@ void HalRosControlLoop::update(long period) hardware_interface_->write(ros_period); } -} // namespace +} // namespace hal_hw_interface // The HAL hardware interface control loop object boost::shared_ptr control_loop_; diff --git a/hal_hw_interface/src/hal_hw_interface.cpp b/hal_hw_interface/src/hal_hw_interface.cpp index e87b827..43293b1 100644 --- a/hal_hw_interface/src/hal_hw_interface.cpp +++ b/hal_hw_interface/src/hal_hw_interface.cpp @@ -66,12 +66,15 @@ void HalHWInterface::init_hal(void (*funct)(void*, long)) HAL_ROS_LOG_INFO(CNAME, "%s: Init joint #%zu %s", CNAME, ix, joint_names_[ix].c_str()); - if (!create_joint_float_pins(ix, &joint_pos_cmd_ptrs_, HAL_OUT, "pos-" - "cmd") || - !create_joint_float_pins(ix, &joint_vel_cmd_ptrs_, HAL_OUT, "vel-" - "cmd") || - !create_joint_float_pins(ix, &joint_eff_cmd_ptrs_, HAL_OUT, "eff-" - "cmd") || + if (!create_joint_float_pins(ix, &joint_pos_cmd_ptrs_, HAL_OUT, + "pos-" + "cmd") || + !create_joint_float_pins(ix, &joint_vel_cmd_ptrs_, HAL_OUT, + "vel-" + "cmd") || + !create_joint_float_pins(ix, &joint_eff_cmd_ptrs_, HAL_OUT, + "eff-" + "cmd") || !create_joint_float_pins(ix, &joint_pos_fb_ptrs_, HAL_IN, "pos-fb") || !create_joint_float_pins(ix, &joint_vel_fb_ptrs_, HAL_IN, "vel-fb") || !create_joint_float_pins(ix, &joint_eff_fb_ptrs_, HAL_IN, "eff-fb")) @@ -212,4 +215,4 @@ void HalHWInterface::shutdown() } } -} // namespace +} // namespace hal_hw_interface diff --git a/hal_hw_interface/src/hal_hw_interface/hal_io_comp.py b/hal_hw_interface/src/hal_hw_interface/hal_io_comp.py index 6abef4d..f8aac52 100644 --- a/hal_hw_interface/src/hal_hw_interface/hal_io_comp.py +++ b/hal_hw_interface/src/hal_hw_interface/hal_io_comp.py @@ -47,8 +47,7 @@ class HalIO(RosHalComponent): compname = 'hal_io' def setup_component(self): - """Load pin configuration from ROS param server and create pin objects - """ + """Load pin configuration from ROS param server and create pin objects""" self.pins = [] pin_class_map = dict( subscribe_pins=RosHalPinSubscriber, @@ -63,7 +62,6 @@ def setup_component(self): self.pins.append(p) def update(self): - """Run pin `update()` functions - """ + """Run pin `update()` functions""" for p in self.pins: p.update() diff --git a/hal_hw_interface/src/hal_hw_interface/hal_obj_base.py b/hal_hw_interface/src/hal_hw_interface/hal_obj_base.py index ee01f0a..1875e0b 100644 --- a/hal_hw_interface/src/hal_hw_interface/hal_obj_base.py +++ b/hal_hw_interface/src/hal_hw_interface/hal_obj_base.py @@ -52,7 +52,7 @@ def hal_comp(self): return self._cached_objs['hal_comp'] def get_ros_param(self, suffix, default=None): - '''Retrieve a parameter from the ROS param server, key name prefixed + """Retrieve a parameter from the ROS param server, key name prefixed with HAL component name This is shorthand for retrieving a ROS param server key @@ -64,7 +64,7 @@ def get_ros_param(self, suffix, default=None): :type default: any :returns: parameter value :rtype: XmlRpcLegalValue - ''' + """ return self._cached_objs.setdefault( suffix, rospy.get_param('{}/{}'.format(self.compname, suffix), default), diff --git a/hal_hw_interface/src/hal_hw_interface/hal_pin_attrs.py b/hal_hw_interface/src/hal_hw_interface/hal_pin_attrs.py index 23c9486..125321d 100644 --- a/hal_hw_interface/src/hal_hw_interface/hal_pin_attrs.py +++ b/hal_hw_interface/src/hal_hw_interface/hal_pin_attrs.py @@ -3,9 +3,9 @@ class HalPinAttrBase(int): - '''Subclass int to make a simple interface for accessing + """Subclass int to make a simple interface for accessing hal.HAL_ enums by integer or string - ''' + """ _suffixes = [] # hal.HAL_%s; define in subclasses _updated = False @@ -22,8 +22,7 @@ class HalPinAttrBase(int): _bwd_map[attr_short] = value def __new__(cls, value): - '''Create new object, translating strings to ints and validating value - ''' + """Create new object, translating strings to ints and validating value""" if isinstance(value, int): if cls._fwd_map.get(value, None) not in cls._suffixes: raise ValueError("Illegal value '{}'".format(value)) diff --git a/hal_hw_interface/src/hal_hw_interface/redis_store_hal_pin.py b/hal_hw_interface/src/hal_hw_interface/redis_store_hal_pin.py index 8e61206..036e873 100644 --- a/hal_hw_interface/src/hal_hw_interface/redis_store_hal_pin.py +++ b/hal_hw_interface/src/hal_hw_interface/redis_store_hal_pin.py @@ -22,7 +22,7 @@ @attr.s class RedisStoreHalPin(RosHalPin): - '''HAL pin attached to :code:`redis_store` ROS package's parameter + """HAL pin attached to :code:`redis_store` ROS package's parameter server This HAL pin's value may be read from and written to a @@ -38,7 +38,7 @@ class RedisStoreHalPin(RosHalPin): :type hal_type: :py:class:`hal_hw_interface.hal_pin_attrs.HalPinType` :param hal_dir: HAL pin direction, one of :code:`['IN', 'OUT', 'IO']` :type hal_dir: :py:class:`hal_hw_interface.hal_pin_attrs.HalPinDir` - ''' + """ key = attr.ib() @@ -47,7 +47,7 @@ class RedisStoreHalPin(RosHalPin): def _key_default(self): return '{}/{}'.format(self.compname, self.pin_name) - redis_service_timeout_default = 20.0 # seconds to wait for service + redis_service_timeout_default = 20.0 # seconds to wait for service @property def _redis_config(self): @@ -56,8 +56,7 @@ def _redis_config(self): # Autovivify timeout = self.get_ros_param( - 'redis_service_timeout', - self.redis_service_timeout_default + 'redis_service_timeout', self.redis_service_timeout_default ) rospy.loginfo(f"Connecting to redis database, timeout {timeout}s") client = redis_config.ConfigClient(subscribe=True) @@ -92,8 +91,7 @@ def _update_fm_redis(self, key, value): self._prev_pin_val = self._prev_redis_val = value def update(self): - """Write changed pin value to redis for input and IO pins - """ + """Write changed pin value to redis for input and IO pins""" new_val = self.get_pin() if self.hal_dir == HalPinDir('OUT') or self._prev_pin_val == new_val: return # Not applicable diff --git a/hal_hw_interface/src/hal_hw_interface/ros_hal_pin.py b/hal_hw_interface/src/hal_hw_interface/ros_hal_pin.py index 1b82503..187ecff 100644 --- a/hal_hw_interface/src/hal_hw_interface/ros_hal_pin.py +++ b/hal_hw_interface/src/hal_hw_interface/ros_hal_pin.py @@ -13,7 +13,6 @@ hal_hw_interface.ros_hal_pin.RosHalPinService """ -import sys import attr import rospy from hal_hw_interface.hal_obj_base import HalObjBase @@ -27,7 +26,7 @@ @attr.s class RosHalPin(HalObjBase): - '''Basic HAL pin for use in + """Basic HAL pin for use in :py:class:`hal_hw_interface.ros_hal_component.RosHalComponent` user components @@ -42,7 +41,7 @@ class RosHalPin(HalObjBase): :type hal_type: :py:class:`hal_hw_interface.hal_pin_attrs.HalPinType` :param hal_dir: HAL pin direction, one of :code:`['IN', 'OUT', 'IO']` :type hal_dir: :py:class:`hal_hw_interface.hal_pin_attrs.HalPinDir` - ''' + """ _default_hal_type = None _default_hal_dir = 'IN' # Subclasses may override for hal_dir attribute @@ -64,13 +63,13 @@ def _hal_type_default(self): @property def pin_name(self): - '''Return the pin_name; read-only property + """Return the pin_name; read-only property In some subclasses, this may not be the same as the :code:`name` parameter supplied to the constructor. :returns: :py:class:`str` pin name - ''' + """ return self.name def __attrs_post_init__(self): @@ -93,8 +92,7 @@ def _ros_init(self): pass def update(self): - '''An update function; used in some subclasses - ''' + """An update function; used in some subclasses""" # May be implemented in subclasses raise NotImplementedError() @@ -116,10 +114,10 @@ def get_pin(self): @property def compname(self): - '''The HAL component name; read-only property + """The HAL component name; read-only property :returns: :py:class:`str` of component name - ''' + """ return self.hal_comp.getprefix() @classmethod @@ -129,7 +127,7 @@ def _isclose(cls, a, b, rel_tol=1e-9, abs_tol=1e-9): @attr.s class RosHalPinPublisher(RosHalPin): - '''HAL pin with attached ROS publisher + """HAL pin with attached ROS publisher This HAL pin is set with publishes its value on a ROS topic, :code:`/` by default. Its :py:func:`update` function @@ -161,7 +159,7 @@ class RosHalPinPublisher(RosHalPin): directions make sense for all subclasses .. todo:: Link documentation to ROS ``srv`` messages - ''' + """ _default_hal_dir = 'IN' pub_topic = attr.ib() @@ -206,21 +204,23 @@ def _value_changed(self, value): return changed def update(self): - """If pin value has changed, publish to ROS topic - """ + """If pin value has changed, publish to ROS topic""" value = self.get_pin() if self._value_changed(value): rospy.logdebug( - "publish_pins: Publishing pin '%s' value '%s'" % - (self.pin_name, self.get_pin())) - rospy.loginfo(f'Pin {self.pin_name} changed: old={self._msg.data}; new={value}') + "publish_pins: Publishing pin '%s' value '%s'" + % (self.pin_name, self.get_pin()) + ) + rospy.loginfo( + f'Pin {self.pin_name} changed: old={self._msg.data}; new={value}' + ) self._msg.data = value self.pub.publish(self._msg) @attr.s class RosHalPinSubscriber(RosHalPinPublisher): - '''HAL pin with attached ROS publisher and subscriber + """HAL pin with attached ROS publisher and subscriber This HAL pin isn't set via :py:func:`set_pin`, but subscribes to a ROS topic for its value. As a subclass of @@ -241,7 +241,7 @@ class RosHalPinSubscriber(RosHalPinPublisher): :type pub_topic: str :param sub_topic: ROS subscriber topic :type sub_topic: str - ''' + """ _default_hal_dir = 'OUT' sub_topic = attr.ib() @@ -273,14 +273,13 @@ def _subscriber_cb(self, msg): ) if self._value_changed(msg.data): - rospy.loginfo(f'Pin {self.pin_name} subscriber change: old={self._msg.data}; new={msg.data}') self.set_pin(msg.data) self.update() @attr.s class RosHalPinService(RosHalPinPublisher): - '''HAL pin with attached ROS service and publisher + """HAL pin with attached ROS service and publisher This HAL pin may be set via a ROS service, in addition to :py:func:`set_pin`. As a subclass of @@ -303,7 +302,7 @@ class RosHalPinService(RosHalPinPublisher): :type pub_topic: str :param service_name: ROS service name :type service_name: str - ''' + """ _default_hal_dir = 'OUT' diff --git a/hal_hw_interface/src/hal_hw_interface/tests/test_redis_store_hal_pin.py b/hal_hw_interface/src/hal_hw_interface/tests/test_redis_store_hal_pin.py index a8ba162..30b1fe3 100644 --- a/hal_hw_interface/src/hal_hw_interface/tests/test_redis_store_hal_pin.py +++ b/hal_hw_interface/src/hal_hw_interface/tests/test_redis_store_hal_pin.py @@ -49,9 +49,12 @@ def test_redis_store_pin_set_pin_from_redis( ) def test_redis_store_pin_set_pin_no_defaults(self, all_patches): - mock_comp_obj, mock_rospy, mock_objs, mock_redis_client_obj = ( - all_patches - ) + ( + mock_comp_obj, + mock_rospy, + mock_objs, + mock_redis_client_obj, + ) = all_patches # Fake a redis param value and set the pin from redis obj = self.test_class('unconfigured_redis_pin', 'FLOAT') self.setup_hal_obj_base(mock_comp_obj) diff --git a/hal_hw_interface/src/hal_hw_interface/tests/test_ros_hal_component.py b/hal_hw_interface/src/hal_hw_interface/tests/test_ros_hal_component.py index 5caadcc..ab90c9b 100644 --- a/hal_hw_interface/src/hal_hw_interface/tests/test_ros_hal_component.py +++ b/hal_hw_interface/src/hal_hw_interface/tests/test_ros_hal_component.py @@ -35,8 +35,7 @@ def test_ros_hal_component_attrs(self, obj): def test_ros_hal_component_init( self, obj, mock_rospy, mock_comp_obj, mock_objs ): - '''Test RosHalComponent.__init__() - ''' + """Test RosHalComponent.__init__()""" # ROS node initialized mock_rospy['init_node'].assert_called_with(obj.compname) # obj.rate was created from rospy.Rate with param value @@ -55,17 +54,16 @@ def test_ros_hal_component_init( obj.hal_comp.ready.assert_called_once_with() def test_ros_hal_component_get_param(self, obj, mock_objs): - '''Test get_param() - ''' + """Test get_param()""" res = obj.get_ros_param('relative_tolerance', 42) assert res == 1e-9 res = obj.get_ros_param('bogus_key', 88) assert res == 88 def test_ros_hal_component_run(self, obj, mock_objs): - '''Test run() (fixture loops three times); should call update() and + """Test run() (fixture loops three times); should call update() and rate.sleep() - ''' + """ obj.run() assert obj.count == 3 assert mock_objs['rospy_Rate_obj'].sleep.call_count == 3 diff --git a/hal_hw_interface/src/hal_hw_interface/tests/test_ros_hal_pin.py b/hal_hw_interface/src/hal_hw_interface/tests/test_ros_hal_pin.py index 2da4667..bd6b879 100644 --- a/hal_hw_interface/src/hal_hw_interface/tests/test_ros_hal_pin.py +++ b/hal_hw_interface/src/hal_hw_interface/tests/test_ros_hal_pin.py @@ -221,8 +221,7 @@ def test_ros_hal_pin_isclose(self, isclose_case): assert self.test_class._isclose(a, b, 1e-9, 1e-9) is res def test_ros_hal_pin_attrs(self, obj, mock_comp_obj): - '''Test that attributes are set as expected, including defaults - ''' + """Test that attributes are set as expected, including defaults""" assert obj.pin_name == self.obj_test_name(obj) assert obj.hal_comp is mock_comp_obj assert type(obj.hal_type) is HalPinType @@ -231,15 +230,13 @@ def test_ros_hal_pin_attrs(self, obj, mock_comp_obj): assert obj.hal_dir == HalPinDir(self.hal_dir(obj)) def test_ros_hal_pin_pin_name(self, obj): - '''Test pin_name generation - ''' + """Test pin_name generation""" assert obj.pin_name == self.obj_test_name(obj) newpin_calls = 1 def test_ros_hal_pin_newpin(self, obj, mock_comp_obj): - '''Test that hal.component.newpin() is called - ''' + """Test that hal.component.newpin() is called""" print(mock_comp_obj.newpin.mock_calls) mock_comp_obj.newpin.assert_any_call( self.obj_test_name(obj), @@ -249,8 +246,7 @@ def test_ros_hal_pin_newpin(self, obj, mock_comp_obj): assert mock_comp_obj.newpin.call_count == self.newpin_calls def test_ros_hal_pin_default_attr_hal_type(self, all_patches): - '''Test default hal_type - ''' + """Test default hal_type""" # This one creates the object in the test, so `all_patches` # needed if self.default_hal_type is None: @@ -282,8 +278,7 @@ def test_ros_hal_pin_attrs(self, obj, mock_comp_obj): assert obj.pub_topic == self.pub_topic(obj) def test_ros_hal_pin_publisher_init(self, obj, mock_objs): - '''Test that __init__() creates rospy.Publisher object - ''' + """Test that __init__() creates rospy.Publisher object""" print(mock_objs['rospy_Publisher'].mock_calls) assert obj.pub_topic == self.pub_topic(obj) mock_objs['rospy_Publisher'].assert_called_with( @@ -292,8 +287,7 @@ def test_ros_hal_pin_publisher_init(self, obj, mock_objs): assert obj.pub is mock_objs['rospy_Publisher_obj'] def test_ros_hal_pin_publisher_value_changed(self, obj, data, mock_objs): - '''Test _value_changed() function - ''' + """Test _value_changed() function""" assert obj.get_ros_param('relative_tolerance', 1e-9) == 1e-9 assert obj.get_ros_param('absolute_tolerance', 1e-9) == 1e-9 @@ -334,16 +328,14 @@ def test_ros_hal_pin_attrs(self, obj, mock_comp_obj): assert obj.sub_topic == self.sub_topic(obj) def test_ros_hal_pin_subscriber_init(self, obj, mock_objs): - '''Test that __init__() creates rospy.Subscriber object - ''' + """Test that __init__() creates rospy.Subscriber object""" mock_objs['rospy_Subscriber'].assert_called_with( self.sub_topic(obj), self.msg_type(obj), obj._subscriber_cb ) assert obj.sub is mock_objs['rospy_Subscriber_obj'] def test_ros_hal_pin_subscriber_cb(self, obj, data, mock_comp_obj): - '''Test that HAL pin is set in subscriber callback - ''' + """Test that HAL pin is set in subscriber callback""" other_value = self.other_value(obj, data) obj._subscriber_cb(self.msg_type(obj)(other_value)) mock_comp_obj.__setitem__.assert_called_with( @@ -351,8 +343,7 @@ def test_ros_hal_pin_subscriber_cb(self, obj, data, mock_comp_obj): ) def test_ros_hal_pin_subscriber_bad_msg_type(self, obj): - '''Test that invalid message types are caught - ''' + """Test that invalid message types are caught""" msg = UInt16(42) with pytest.raises(HalHWInterfaceException): obj._subscriber_cb(msg) @@ -385,8 +376,7 @@ def test_ros_hal_pin_attrs(self, obj, mock_comp_obj): assert obj.service_msg_type == self.service_msg_type(obj) def test_ros_hal_pin_service_init(self, obj, mock_objs): - '''Test that __init__() creates rospy.Service object - ''' + """Test that __init__() creates rospy.Service object""" assert obj.service_name == self.service_name(obj) mock_objs['rospy_Service'].assert_called_with( self.service_name(obj), self.service_msg_type(obj), obj._svc_cb @@ -394,8 +384,7 @@ def test_ros_hal_pin_service_init(self, obj, mock_objs): assert obj.service is mock_objs['rospy_Service_obj'] def test_ros_hal_pin_service_cb(self, obj, data): - '''Test that the HAL pin is set during callback - ''' + """Test that the HAL pin is set during callback""" msg = self.service_msg_type(obj) call_value = self.other_value(obj, data) msg.data = call_value From da1b7c67b3287651aa105d4f0cb9f7358550fc9c Mon Sep 17 00:00:00 2001 From: John Morris Date: Wed, 19 May 2021 00:15:30 -0500 Subject: [PATCH 11/32] Silence CMake CMP0048 warnings CMake Warning (dev) at CMakeLists.txt:2 (project): Policy CMP0048 is not set: project() command manages VERSION variables. Run "cmake --help-policy CMP0048" for policy details. Use the cmake_policy command to set the policy and suppress this warning. The following variable(s) would be set to empty: CMAKE_PROJECT_VERSION CMAKE_PROJECT_VERSION_MAJOR CMAKE_PROJECT_VERSION_MINOR CMAKE_PROJECT_VERSION_PATCH This warning is for project developers. Use -Wno-dev to suppress it. --- hal_hw_interface/CMakeLists.txt | 2 +- hal_rrbot_control/CMakeLists.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/hal_hw_interface/CMakeLists.txt b/hal_hw_interface/CMakeLists.txt index 93b5cfe..8b70268 100644 --- a/hal_hw_interface/CMakeLists.txt +++ b/hal_hw_interface/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 2.8.3) +cmake_minimum_required(VERSION 3.0.2) project(hal_hw_interface) # C++ 11 diff --git a/hal_rrbot_control/CMakeLists.txt b/hal_rrbot_control/CMakeLists.txt index 2991dbd..6d6cd68 100644 --- a/hal_rrbot_control/CMakeLists.txt +++ b/hal_rrbot_control/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 2.8.3) +cmake_minimum_required(VERSION 3.0.5) project(hal_rrbot_control) find_package( From 66a41e2c2dcdd6caad90ef3111b9926d6d391b1f Mon Sep 17 00:00:00 2001 From: John Morris Date: Fri, 21 May 2021 17:23:51 -0500 Subject: [PATCH 12/32] Add CMake & python to build and load HAL comps from other packages Everything a ROS package needs to build a HAL instcomp: - `FindHAL.cmake` locates HAL include directory and `instcomp` script - `UseHALComp.cmake` builds and installs the comp with `hal_add_instcomp` - Export the `machinekit-dev` build requirement - `loadrt_local()` Python function loads the HAL comp from non-standard location The CMake config really belongs in Machinekit. --- hal_hw_interface/CMakeLists.txt | 17 +++-- hal_hw_interface/cmake/FindHAL.cmake | 9 ++- hal_hw_interface/cmake/UseHALComp.cmake | 65 +++++++++++++++++++ ...al_hw_interface-extras.cmake.develspace.in | 2 + ..._hw_interface-extras.cmake.installspace.in | 2 + hal_hw_interface/package.xml | 27 ++++---- .../src/hal_hw_interface/__init__.py | 4 ++ .../src/hal_hw_interface/loadrt_local.py | 19 ++++++ 8 files changed, 128 insertions(+), 17 deletions(-) create mode 100644 hal_hw_interface/cmake/UseHALComp.cmake create mode 100644 hal_hw_interface/cmake/hal_hw_interface-extras.cmake.develspace.in create mode 100644 hal_hw_interface/cmake/hal_hw_interface-extras.cmake.installspace.in create mode 100644 hal_hw_interface/src/hal_hw_interface/loadrt_local.py diff --git a/hal_hw_interface/CMakeLists.txt b/hal_hw_interface/CMakeLists.txt index 8b70268..624cece 100644 --- a/hal_hw_interface/CMakeLists.txt +++ b/hal_hw_interface/CMakeLists.txt @@ -16,6 +16,12 @@ find_package(catkin REQUIRED COMPONENTS redis_store ) +# Find Machinekit HAL +set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${PROJECT_SOURCE_DIR}/cmake) +find_package( + HAL + ) + # Install packages with setup.py catkin_python_setup() @@ -39,11 +45,10 @@ catkin_package( rospy message_runtime redis_store -) -# Find Machinekit HAL -set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${PROJECT_SOURCE_DIR}/cmake) -find_package(HAL) + CFG_EXTRAS + ${PROJECT_NAME}-extras.cmake +) # Header files # - Specify additional header locations @@ -115,6 +120,10 @@ install( install(DIRECTORY launch DESTINATION ${CATKIN_PACKAGE_SHARE_DESTINATION}) +# Install CMake files +install(FILES cmake/FindHAL.cmake cmake/UseHALComp.cmake + DESTINATION ${CATKIN_PACKAGE_SHARE_DESTINATION}/cmake) + if(CATKIN_ENABLE_TESTING) find_package(rostest REQUIRED) find_package(ros_pytest REQUIRED) diff --git a/hal_hw_interface/cmake/FindHAL.cmake b/hal_hw_interface/cmake/FindHAL.cmake index d49bd08..fb7fce3 100644 --- a/hal_hw_interface/cmake/FindHAL.cmake +++ b/hal_hw_interface/cmake/FindHAL.cmake @@ -6,6 +6,7 @@ # # - HAL_FOUND: Boolean that indicates if the package was found # - HAL_INCLUDE_DIRS: Absolute path to package headers +# - HAL_INSTCOMP: Path to instcomp script # # Example usage: # @@ -17,8 +18,11 @@ # ############################################################################### -set(MACHINEKIT_RIP_PATH $ENV{EMC2_HOME} CACHE STRING "") +set(MACHINEKIT_RIP_PATH + $ENV{MK_HOME} + CACHE STRING "") +# HAL_INCLUDE_PATH: Find HAL include directory find_path( HAL_INCLUDE_PATH hal.h PATH_SUFFIXES machinekit @@ -30,3 +34,6 @@ if(HAL_INCLUDE_PATH) else(HAL_INCLUDE_PATH) message(FATAL_ERROR "Could not find HAL includes") endif(HAL_INCLUDE_PATH) + +# HAL_EXECUTABLE: instcomp python script path +find_program(HAL_INSTCOMP NAMES instcomp) diff --git a/hal_hw_interface/cmake/UseHALComp.cmake b/hal_hw_interface/cmake/UseHALComp.cmake new file mode 100644 index 0000000..a6d8501 --- /dev/null +++ b/hal_hw_interface/cmake/UseHALComp.cmake @@ -0,0 +1,65 @@ +# Define a function to create HAL comps. +# +# This file defines a CMake function to build a HAL component. +# To use it, first include this file. +# +# include(UseHALComp) +# +# Then call `hal_add_comp_module()` to create a component; e.g. if the +# source file is `src/mycomp.icomp`: +# +# hal_comp_add_module(src/mycomp) +# +# The function will generate the C source with `instcomp`, build the +# comp and install it. + +#============================================================================= +# Copyright 2015 John Morris +# +# Permission is hereby granted, free of charge, to any person +# obtaining a copy of this software and associated documentation files +# (the "Software"), to deal in the Software without restriction, +# including without limitation the rights to use, copy, modify, merge, +# publish, distribute, sublicense, and/or sell copies of the Software, +# and to permit persons to whom the Software is furnished to do so, +# subject to the following conditions: +# +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. +# ============================================================================= + +find_package(HAL) + +# hal_comp_add_module(src/my_mod) +function(hal_add_instcomp icomp_modpath) + get_filename_component(icomp_name ${icomp_modpath} NAME) + get_filename_component(icomp_dir ${icomp_modpath} DIRECTORY) + set(icomp_src "${icomp_name}.icomp") + set(icomp_c "${icomp_name}.c") + set(icomp_src_path ${CMAKE_CURRENT_SOURCE_DIR}/${icomp_dir}/${icomp_src}) + + # Generate C source with `instcomp` + add_custom_command( + OUTPUT ${icomp_c} + # Copy .icomp file: instcomp generates .c in same directory + COMMAND cp ${icomp_src_path} ${icomp_src} + COMMAND ${HAL_INSTCOMP} -p ${icomp_src} + DEPENDS ${icomp_src_path} + COMMENT "Preprocessing instcomp ${icomp_modpath}") + + # Add the generated .c target + add_custom_target(${icomp_c} DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/${icomp_src}) + + # Add the HAL comp .so target + add_library(${icomp_name} MODULE ${icomp_c}) + + # Add CFLAGS + target_compile_definitions(${icomp_name} PRIVATE RTAPI=1) + + # Omit the `lib` prefix + set_target_properties(${icomp_name} PROPERTIES PREFIX "") + + # Install HAL component + install(TARGETS ${icomp_name} + LIBRARY DESTINATION ${CATKIN_PACKAGE_LIB_DESTINATION}) +endfunction() diff --git a/hal_hw_interface/cmake/hal_hw_interface-extras.cmake.develspace.in b/hal_hw_interface/cmake/hal_hw_interface-extras.cmake.develspace.in new file mode 100644 index 0000000..8d8014a --- /dev/null +++ b/hal_hw_interface/cmake/hal_hw_interface-extras.cmake.develspace.in @@ -0,0 +1,2 @@ +# Append cmake modules from source directory to the cmake module path +list(APPEND CMAKE_MODULE_PATH @CMAKE_CURRENT_SOURCE_DIR@/cmake) diff --git a/hal_hw_interface/cmake/hal_hw_interface-extras.cmake.installspace.in b/hal_hw_interface/cmake/hal_hw_interface-extras.cmake.installspace.in new file mode 100644 index 0000000..d4241ef --- /dev/null +++ b/hal_hw_interface/cmake/hal_hw_interface-extras.cmake.installspace.in @@ -0,0 +1,2 @@ +# Append cmake modules from source directory to the cmake module path +list(APPEND CMAKE_MODULE_PATH "${hal_hw_interface_DIR}/../../../@CATKIN_PACKAGE_SHARE_DESTINATION@/cmake") diff --git a/hal_hw_interface/package.xml b/hal_hw_interface/package.xml index f6dddfb..4838a8e 100644 --- a/hal_hw_interface/package.xml +++ b/hal_hw_interface/package.xml @@ -1,5 +1,5 @@ - + hal_hw_interface 0.0.0 @@ -27,17 +27,20 @@ roslaunch machinekit-dev - ros_control_boilerplate - controller_manager - roscpp - rospy - python3-attrs - std_msgs - std_srvs - message_runtime - redis_store - rosbash - machinekit + + machinekit-dev + + ros_control_boilerplate + controller_manager + roscpp + rospy + python3-attrs + std_msgs + std_srvs + message_runtime + redis_store + rosbash + machinekit ros_pytest diff --git a/hal_hw_interface/src/hal_hw_interface/__init__.py b/hal_hw_interface/src/hal_hw_interface/__init__.py index ca0c2c8..9bb2a97 100644 --- a/hal_hw_interface/src/hal_hw_interface/__init__.py +++ b/hal_hw_interface/src/hal_hw_interface/__init__.py @@ -4,3 +4,7 @@ .. moduleauthor:: John Morris """ + +__all__ = ('loadrt_local', 'hal', 'rtapi') + +from .loadrt_local import loadrt_local, hal, rtapi diff --git a/hal_hw_interface/src/hal_hw_interface/loadrt_local.py b/hal_hw_interface/src/hal_hw_interface/loadrt_local.py new file mode 100644 index 0000000..940553a --- /dev/null +++ b/hal_hw_interface/src/hal_hw_interface/loadrt_local.py @@ -0,0 +1,19 @@ +import rospy +import os +from machinekit import rtapi, hal + + +def loadrt_local(modname): + """Load a locally-built HAL component not installed in the standard + module directory + """ + if modname in hal.components: + return + for path in os.environ.get('LD_LIBRARY_PATH', '').split(':'): + rospy.logdebug(f"Checking for {modname}.so in {path}") + modpath = os.path.join(path, f'{modname}') + if os.path.exists(f'{modpath}.so'): + break + else: + raise RuntimeError(f'Unable to locate {modname} module') + rtapi.loadrt(modpath) From e34bf5d6e5faac8f70330a20d420e3f85d3dfd2a Mon Sep 17 00:00:00 2001 From: John Morris Date: Tue, 18 May 2021 12:54:45 -0500 Subject: [PATCH 13/32] hal_hw_interface: Move code out of constructor & check for failures Completes a TODO in the code --- .../hal_hw_interface/hal_control_loop.h | 15 ++++++++++----- .../hal_hw_interface/hal_hw_interface.h | 3 +-- hal_hw_interface/src/hal_control_loop.cpp | 14 ++++++++++---- hal_hw_interface/src/hal_hw_interface.cpp | 18 +++++++----------- 4 files changed, 28 insertions(+), 22 deletions(-) diff --git a/hal_hw_interface/include/hal_hw_interface/hal_control_loop.h b/hal_hw_interface/include/hal_hw_interface/hal_control_loop.h index 72a1da9..9977035 100644 --- a/hal_hw_interface/include/hal_hw_interface/hal_control_loop.h +++ b/hal_hw_interface/include/hal_hw_interface/hal_control_loop.h @@ -62,11 +62,6 @@ class HalRosControlLoop /** * \brief Constructor * - * The constructor: - * * Sets up the ROS node - * * Runs the ROS spinner thread - * * Initializes the `hal_hw_interface::HalHWInterface` object - * * Initializes the `controller_manager::ControllerManager` object */ HalRosControlLoop(); @@ -77,6 +72,16 @@ class HalRosControlLoop */ ~HalRosControlLoop(); + /** + * \brief Initialize control loop object + * + * * Set up the ROS node + * * Run the ROS spinner thread + * * Initialize the `hal_hw_interface::HalHWInterface` object + * * Initialize the `controller_manager::ControllerManager` object + */ + int init(); + /** * \brief Run one ros_control `read()/update()/write()` cycle * diff --git a/hal_hw_interface/include/hal_hw_interface/hal_hw_interface.h b/hal_hw_interface/include/hal_hw_interface/hal_hw_interface.h index 86ba359..838e229 100644 --- a/hal_hw_interface/include/hal_hw_interface/hal_hw_interface.h +++ b/hal_hw_interface/include/hal_hw_interface/hal_hw_interface.h @@ -95,9 +95,8 @@ class HalHWInterface : public ros_control_boilerplate::GenericHWInterface * * Initializes the HAL component and sets up HAL pins for each joint. */ - //* \todo Give this an int return value for reporting failure //* \todo Make the `reset` pin an IO pin - void init_hal(void (*funct)(void*, long)); + int init_hal(void (*funct)(void*, long)); /** * \brief Create float-type HAL pins for each joint diff --git a/hal_hw_interface/src/hal_control_loop.cpp b/hal_hw_interface/src/hal_control_loop.cpp index f7eb6c8..a6ef826 100644 --- a/hal_hw_interface/src/hal_control_loop.cpp +++ b/hal_hw_interface/src/hal_control_loop.cpp @@ -40,6 +40,10 @@ extern "C" void funct(void* arg, long period); namespace hal_hw_interface { HalRosControlLoop::HalRosControlLoop() : node_is_shutdown(0) +{ +} + +int HalRosControlLoop::init() { // ROS node handle nh_.reset(new ros::NodeHandle("")); @@ -74,13 +78,16 @@ HalRosControlLoop::HalRosControlLoop() : node_is_shutdown(0) CNAME); // Init HAL hardware interface - hardware_interface_->init_hal(&funct); + if (hardware_interface_->init_hal(&funct) != 0) + return 1; // Failure HAL_ROS_LOG_INFO(CNAME, "%s: Done initializing HAL hardware interface", CNAME); HAL_ROS_LOG_INFO(CNAME, "HAL control loop ready."); -} // constructor + + return 0; // Success +} // init // Non-RT thread CB function void HalRosControlLoop::serviceNonRtRosQueue() @@ -157,8 +164,7 @@ int rtapi_app_main(void) // Create HAL controller and hardware interface control_loop_.reset(new hal_hw_interface::HalRosControlLoop()); - - return 0; + return control_loop_->init(); } void funct(void* arg, long period) diff --git a/hal_hw_interface/src/hal_hw_interface.cpp b/hal_hw_interface/src/hal_hw_interface.cpp index 43293b1..6c6a566 100644 --- a/hal_hw_interface/src/hal_hw_interface.cpp +++ b/hal_hw_interface/src/hal_hw_interface.cpp @@ -39,7 +39,7 @@ HalHWInterface::HalHWInterface(ros::NodeHandle& nh, urdf::Model* urdf_model) { } -void HalHWInterface::init_hal(void (*funct)(void*, long)) +int HalHWInterface::init_hal(void (*funct)(void*, long)) { HAL_ROS_LOG_INFO(CNAME, "%s: Initializing HAL hardware interface", CNAME); @@ -53,8 +53,7 @@ void HalHWInterface::init_hal(void (*funct)(void*, long)) if (comp_id_ < 0) { HAL_ROS_LOG_ERR(CNAME, "%s: ERROR: Component creation ABORTED", CNAME); - // return false; // FIXME - return; + return false; } HAL_ROS_LOG_INFO(CNAME, "%s: Initialized HAL component", CNAME); @@ -81,17 +80,15 @@ void HalHWInterface::init_hal(void (*funct)(void*, long)) { HAL_ROS_LOG_ERR(CNAME, "%s: Failed to initialize joint %zu %s.%s", CNAME, ix, CNAME, joint_names_[ix].c_str()); - // return false; // FIXME - return; + return false; } } - // Initialize started pin + // Initialize reset pin if (!create_bit_pin(&reset_ptr_, HAL_IN, "reset")) { HAL_ROS_LOG_ERR(CNAME, "%s: Failed to initialize reset pin", CNAME); - // return false; // FIXME - return; + return false; } HAL_ROS_LOG_INFO(CNAME, "%s: Initialized HAL pins", CNAME); @@ -101,8 +98,7 @@ void HalHWInterface::init_hal(void (*funct)(void*, long)) { HAL_ROS_LOG_INFO(CNAME, "%s: ERROR: hal_export_functf failed", CNAME); hal_exit(comp_id_); - // return false; // FIXME - return; + return false; } HAL_ROS_LOG_INFO(CNAME, "%s: Exported HAL function", CNAME); @@ -111,7 +107,7 @@ void HalHWInterface::init_hal(void (*funct)(void*, long)) HAL_ROS_LOG_INFO(CNAME, "%s: HAL component ready!", CNAME); - // return true; // FIXME + return true; } // init() bool HalHWInterface::create_joint_float_pins(const std::size_t ix, From bd8ab26ce91b4fcc63b1517da0608b30b42e7361 Mon Sep 17 00:00:00 2001 From: John Morris Date: Fri, 17 Sep 2021 22:51:39 -0500 Subject: [PATCH 14/32] .github: Migrate to industrial_ci --- .github/workflows/catkin_tools_devel.sh | 15 ++ .github/workflows/ci.yaml | 196 ++++++++++-------------- .github/workflows/docker.yaml | 174 +++++++++++++++++++++ .github/workflows/format.yaml | 38 +++++ .github/workflows/prerelease.yaml | 38 +++++ .github/workflows/upstream.rosinstall | 8 + .github/workflows/upstream_install.sh | 47 ++++++ 7 files changed, 399 insertions(+), 117 deletions(-) create mode 100644 .github/workflows/catkin_tools_devel.sh create mode 100644 .github/workflows/docker.yaml create mode 100644 .github/workflows/format.yaml create mode 100644 .github/workflows/prerelease.yaml create mode 100644 .github/workflows/upstream.rosinstall create mode 100755 .github/workflows/upstream_install.sh diff --git a/.github/workflows/catkin_tools_devel.sh b/.github/workflows/catkin_tools_devel.sh new file mode 100644 index 0000000..fa1c4f5 --- /dev/null +++ b/.github/workflows/catkin_tools_devel.sh @@ -0,0 +1,15 @@ +#!/bin/bash + +# Custome BUILDER to build in devel-space only + +BUILDER=catkin_tools ici_source_builder + +function ici_extend_space { + echo "$1/devel" +} + +function _catkin_config { + local extend=$1; shift + local ws=$1; shift + ici_exec_in_workspace "$extend" "$ws" catkin config --init +} diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 9d43853..518d4ae 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -1,130 +1,92 @@ -name: Build CI +# This config uses industrial_ci +# (https://github.com/ros-industrial/industrial_ci.git). For +# troubleshooting, see readme +# (https://github.com/ros-industrial/industrial_ci/blob/master/README.rst) + +name: CI on: - push: - branches: - - '**' + workflow_dispatch: pull_request: + push: branches: - - '*' + - master + - "*-devel" jobs: - - ###################################################################################### - prepareState: - name: Prepare build inputs - runs-on: ubuntu-latest - outputs: - GithubRegistryURL: ${{ steps.build_inputs.outputs.GithubRegistryURL }} - HasCloudsmithAPIKey: ${{ steps.build_inputs.outputs.HasCloudsmithAPIKey }} - MainMatrix: ${{ steps.build_inputs.outputs.MainMatrix }} - Timestamp: ${{ steps.build_inputs.outputs.Timestamp }} - - steps: - - name: Clone repository - uses: actions/checkout@v2 - - - name: Install Python for build scripts - uses: actions/setup-python@v2 - with: - python-version: '3.8' - - - name: Install script dependencies - uses: zultron/machinekit_ci/actions/initDeps@v1 - - - name: Prepare build inputs - id: build_inputs - uses: zultron/machinekit_ci/actions/prepareState@v1 - with: - CloudsmithAPIKey: ${{ secrets.CLOUDSMITH_API_KEY }} - - ###################################################################################### - buildPackages: - name: > - Package ${{ matrix.vendor }} ${{ matrix.codename }}, ${{ matrix.architecture }} - runs-on: ubuntu-latest - needs: prepareState + default: strategy: - matrix: ${{ fromJson(needs.prepareState.outputs.MainMatrix) }} fail-fast: false - - steps: - - name: Clone git repository - uses: actions/checkout@v2 - - - name: Prepare specific Python version for build scripts - uses: actions/setup-python@v2 - with: - python-version: '3.8' - - - name: Install script dependencies - uses: zultron/machinekit_ci/actions/initDeps@v1 - - - name: Pull or build Docker image - id: docker_image - uses: zultron/machinekit_ci/actions/dockerImage@v1 - with: - codename: ${{ matrix.codename }} - architecture: ${{ matrix.architecture }} - dockerRegistryURL: ${{ needs.prepareState.outputs.GithubRegistryURL }} - dockerRegistryRepo: ${{ github.event.repository.name }} - dockerRegistryUser: ${{ github.actor }} - dockerRegistryPassword: ${{ secrets.GITHUB_TOKEN }} - - - name: Build and sign packages - id: build_packages - uses: zultron/machinekit_ci/actions/buildPackages@v1 - with: - codename: ${{ matrix.codename }} - architecture: ${{ matrix.architecture }} - dockerRegistryURL: ${{ needs.prepareState.outputs.GithubRegistryURL }} - dockerRegistryRepo: ${{ github.event.repository.name }} - dockerRegistryUser: ${{ github.actor }} - packageSigningKey: ${{ secrets.PACKAGE_SIGNING_KEY }} - uploadDirectory: ${{ github.event.repository.name }}-${{ matrix.vendorLower }} - - - name: > - Upload package artifacts ${{ matrix.vendor }} - ${{ matrix.codename}}, ${{ matrix.architecture }} - uses: actions/upload-artifact@v2 - env: - NAME_BASE: ${{ matrix.artifactNameBase }} - GITHUB_SHA: ${{ github.sha }} - GITHUB_REPO: ${{ github.event.repository.name }} - TIMESTAMP: ${{ needs.prepareState.outputs.Timestamp }} - with: - name: ${{ env.NAME_BASE }}-${{ env.GITHUB_SHA }}-${{ env.TIMESTAMP }} - path: ${{ github.event.repository.name }}-${{ matrix.vendorLower }} - if-no-files-found: error - - ###################################################################################### - uploadDebianPackagesToCloudsmith: - name: Push packages to Cloudsmith + matrix: + env: + - IMAGE: noetic-ci + BUILDER: .github/workflows/catkin_tools_devel.sh + - IMAGE: noetic-ci-shadow-fixed + CATKIN_LINT: true + CLANG_TIDY: pedantic + env: + DOCKER_IMAGE: moveit/moveit:${{ matrix.env.IMAGE }} + UPSTREAM_WORKSPACE: .github/workflows/upstream.rosinstall + BEFORE_SETUP_UPSTREAM_WORKSPACE: .github/workflows/upstream_install.sh + + TARGET_CMAKE_ARGS: > + -DCMAKE_BUILD_TYPE=Release' + -DCMAKE_CXX_FLAGS="-Werror $CXXFLAGS" + CCACHE_DIR: ${{ github.workspace }}/.ccache + BASEDIR: ${{ github.workspace }}/.work + CLANG_TIDY_BASE_REF: ${{ github.base_ref || github.ref }} + BEFORE_CLANG_TIDY_CHECKS: (cd $TARGET_REPO_PATH; clang-tidy --list-checks) + BUILDER: ${{ matrix.env.BUILDER || 'catkin_tools' }} + CC: ${{ matrix.env.CLANG_TIDY && 'clang' }} + CXX: ${{ matrix.env.CLANG_TIDY && 'clang++' }} + + name: "${{ matrix.env.IMAGE }}${{ matrix.env.CATKIN_LINT && ' + catkin_lint' || ''}}${{ matrix.env.CLANG_TIDY && ' + clang-tidy' || '' }}" runs-on: ubuntu-latest - if: > - needs.prepareState.outputs.HasCloudsmithAPIKey == 'true' && - github.event_name == 'push' - needs: [prepareState, buildPackages] - steps: - - name: Clone git repository - uses: actions/checkout@v2 - - - name: Prepare specific Python version for Cloudsmith CLI - uses: actions/setup-python@v2 + - uses: actions/checkout@v2 + - name: cache upstream workspace + uses: pat-s/always-upload-cache@v2.1.5 with: - python-version: '3.8' - - - name: Install script dependencies - uses: zultron/machinekit_ci/actions/initDeps@v1 - - - name: Download all built artifacts from GitHub storage - uses: actions/download-artifact@v2 + path: ${{ env.BASEDIR }}/upstream_ws + key: ${{ env.CACHE_PREFIX }}-${{ github.run_id }} + restore-keys: ${{ env.CACHE_PREFIX }} + env: + CACHE_PREFIX: upstream_ws-${{ matrix.env.IMAGE }}-${{ hashFiles('.github/workflows/upstream.rosinstall', '.github/workflows/ci.yaml') }} + # The target directory cache doesn't include the source directory because + # that comes from the checkout. See "prepare target_ws for cache" task below + - name: cache target workspace + uses: pat-s/always-upload-cache@v2.1.5 with: - path: ./artifacts - - - name: Upload packages to Cloudsmith - uses: zultron/machinekit_ci/actions/pushCloudsmith@v1 + path: ${{ env.BASEDIR }}/target_ws + key: ${{ env.CACHE_PREFIX }}-${{ github.run_id }} + restore-keys: ${{ env.CACHE_PREFIX }} + env: + CACHE_PREFIX: target_ws-${{ matrix.env.IMAGE }}-${{ hashFiles('**/CMakeLists.txt', '**/package.xml', '.github/workflows/ci.yaml') }} + - name: cache ccache + uses: pat-s/always-upload-cache@v2.1.5 + with: + path: ${{ env.CCACHE_DIR }} + key: ${{ env.CACHE_PREFIX }}-${{ github.sha }}-${{ github.run_id }} + restore-keys: | + ${{ env.CACHE_PREFIX }}-${{ github.sha }} + ${{ env.CACHE_PREFIX }} + env: + CACHE_PREFIX: ccache-${{ matrix.env.IMAGE }} + + - id: industrial_ci + uses: ros-industrial/industrial_ci@master + env: ${{ matrix.env }} + + - name: upload test artifacts (on failure) + uses: actions/upload-artifact@v2 + if: steps.industrial_ci.outcome != 'success' with: - cloudsmithAPIKey: ${{ secrets.CLOUDSMITH_API_KEY }} - artifactDirectory: ./artifacts + name: test-results-${{ matrix.env.IMAGE }} + path: ${{ env.BASEDIR }}/target_ws/**/test_results/**/*.xml + - name: prepare target_ws for cache + if: ${{ always() }} + run: | + du -sh ${{ env.BASEDIR }}/target_ws + sudo find ${{ env.BASEDIR }}/target_ws -wholename '*/test_results/*' -delete + sudo rm -rf ${{ env.BASEDIR }}/target_ws/src + du -sh ${{ env.BASEDIR }}/target_ws diff --git a/.github/workflows/docker.yaml b/.github/workflows/docker.yaml new file mode 100644 index 0000000..d261e0b --- /dev/null +++ b/.github/workflows/docker.yaml @@ -0,0 +1,174 @@ +name: docker + +on: + schedule: + # 6 AM UTC every Sunday + - cron: "0 6 * * 6" + workflow_dispatch: + push: + branches: + - master + +jobs: + release: + strategy: + fail-fast: false + matrix: + ROS_DISTRO: [noetic] + runs-on: ubuntu-latest + permissions: + packages: write + contents: read + env: + IMAGE: moveit/moveit:${{ matrix.ROS_DISTRO }}-${{ github.job }} + + steps: + - uses: addnab/docker-run-action@v3 + name: Check for apt updates + id: apt + with: + image: ${{ env.IMAGE }} + run: | + apt-get update + have_updates=$(apt-get --simulate upgrade | grep -q "^0 upgraded, 0 newly installed, 0 to remove and 0 not upgraded.$" && echo false || echo true) + echo "::set-output name=no_cache::$have_updates" + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v1 + if: ${{ github.event_name == 'workflow_dispatch' || github.event_name != 'schedule' || steps.apt.outputs.no_cache }} + - name: Login to Container Registry + uses: docker/login-action@v1 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + - name: Build and Push + uses: docker/build-push-action@v2 + if: ${{ github.event_name == 'workflow_dispatch' || github.event_name != 'schedule' || steps.apt.outputs.no_cache }} + with: + file: .docker/${{ github.job }}/Dockerfile + build-args: ROS_DISTRO=${{ matrix.ROS_DISTRO }} + push: true + no-cache: ${{ steps.apt.outputs.no_cache || github.event_name == 'workflow_dispatch' }} + cache-from: type=registry,ref=${{ env.IMAGE }} + cache-to: type=inline + tags: ${{ env.IMAGE }} + + ci: + strategy: + fail-fast: false + matrix: + IMAGE: [noetic, master] + runs-on: ubuntu-latest + permissions: + packages: write + contents: read + env: + IMAGE: moveit/moveit:${{ matrix.IMAGE }}-${{ github.job }} + ROS_DISTRO: ${{ matrix.IMAGE == 'master' && 'melodic' || 'noetic' }} + + steps: + - uses: addnab/docker-run-action@v3 + name: Check for apt updates + id: apt + with: + image: ${{ env.IMAGE }} + run: | + apt-get update + have_updates=$(apt-get --simulate upgrade | grep -q "^0 upgraded, 0 newly installed, 0 to remove and 0 not upgraded.$" && echo false || echo true) + echo "::set-output name=no_cache::$have_updates" + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v1 + if: ${{ github.event_name == 'workflow_dispatch' || github.event_name != 'schedule' || steps.apt.outputs.no_cache }} + - name: Login to Container Registry + uses: docker/login-action@v1 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + - name: Build and Push + uses: docker/build-push-action@v2 + if: ${{ github.event_name == 'workflow_dispatch' || github.event_name != 'schedule' || steps.apt.outputs.no_cache }} + with: + file: .docker/${{ github.job }}/Dockerfile + build-args: ROS_DISTRO=${{ env.ROS_DISTRO }} + push: true + no-cache: ${{ steps.apt.outputs.no_cache || github.event_name == 'workflow_dispatch' }} + cache-from: type=registry,ref=${{ env.IMAGE }} + cache-to: type=inline + tags: ${{ env.IMAGE }} + + ci-testing: + needs: ci + strategy: + fail-fast: false + matrix: + IMAGE: [noetic, master] + runs-on: ubuntu-latest + permissions: + packages: write + contents: read + env: + IMAGE: moveit/moveit:${{ matrix.IMAGE }}-${{ github.job }} + + steps: + - uses: addnab/docker-run-action@v3 + name: Check for apt updates + id: apt + with: + image: ${{ env.IMAGE }} + run: | + apt-get update + have_updates=$(apt-get --simulate upgrade | grep -q "^0 upgraded, 0 newly installed, 0 to remove and 0 not upgraded.$" && echo false || echo true) + echo "::set-output name=no_cache::$have_updates" + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v1 + if: ${{ github.event_name == 'workflow_dispatch' || github.event_name != 'schedule' || steps.apt.outputs.no_cache }} + - name: Login to Container Registry + uses: docker/login-action@v1 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + - name: Build and Push + uses: docker/build-push-action@v2 + if: ${{ github.event_name == 'workflow_dispatch' || github.event_name != 'schedule' || steps.apt.outputs.no_cache }} + with: + file: .docker/${{ github.job }}/Dockerfile + build-args: IMAGE=${{ matrix.IMAGE }} + push: true + no-cache: ${{ steps.apt.outputs.no_cache || github.event_name == 'workflow_dispatch' }} + cache-from: type=registry,ref=${{ env.IMAGE }} + cache-to: type=inline + tags: | + ${{ env.IMAGE }} + moveit/moveit:${{ matrix.IMAGE }}-ci-shadow-fixed + + source: + needs: ci-testing + strategy: + fail-fast: false + matrix: + IMAGE: [noetic, master] + runs-on: ubuntu-latest + permissions: + packages: write + contents: read + env: + IMAGE: moveit/moveit:${{ matrix.IMAGE }}-${{ github.job }} + + steps: + - uses: actions/checkout@v2 + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v1 + - name: Login to Container Registry + uses: docker/login-action@v1 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + - name: Build and Push + uses: docker/build-push-action@v2 + with: + context: . + file: .docker/${{ github.job }}/Dockerfile + build-args: IMAGE=${{ matrix.IMAGE }} + push: true + cache-from: type=registry,ref=${{ env.IMAGE }} + cache-to: type=inline + tags: ${{ env.IMAGE }} diff --git a/.github/workflows/format.yaml b/.github/workflows/format.yaml new file mode 100644 index 0000000..22130a3 --- /dev/null +++ b/.github/workflows/format.yaml @@ -0,0 +1,38 @@ +# This is a format job. Pre-commit has a first-party GitHub action, so we use +# that: https://github.com/pre-commit/action + +name: Format + +on: + workflow_dispatch: + pull_request: + push: + +jobs: + pre-commit: + name: pre-commit + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-python@v2 + - name: Install clang-format-10 + run: sudo apt-get install clang-format-10 + - name: Install catkin-lint + run: | + lsb_release -sc + sudo sh -c 'echo "deb http://packages.ros.org/ros/ubuntu $(lsb_release -sc) main" > /etc/apt/sources.list.d/ros-latest.list' + sudo apt-key adv --keyserver 'hkp://keyserver.ubuntu.com:80' --recv-key C1CF6E31E6BADE8868B172B4F42ED6FBAB17C654 + sudo apt-get -q update + sudo apt-get -q install python3-rosdep + sudo rosdep init + rosdep update + sudo apt-get -q install catkin-lint + export ROS_DISTRO=noetic + - name: Install black + run: sudo -H pip3 install black + - name: Install shfmt + run: | + sudo apt-get install -y golang + sudo GOPATH=/usr/local/go GO111MODULE=on go get mvdan.cc/sh/v3/cmd/shfmt + sudo ln -s ../go/bin/shfmt /usr/local/bin/shfmt + - uses: pre-commit/action@v2.0.0 diff --git a/.github/workflows/prerelease.yaml b/.github/workflows/prerelease.yaml new file mode 100644 index 0000000..5c8b6b8 --- /dev/null +++ b/.github/workflows/prerelease.yaml @@ -0,0 +1,38 @@ +# This config uses industrial_ci (https://github.com/ros-industrial/industrial_ci.git). +# For troubleshooting, see readme (https://github.com/ros-industrial/industrial_ci/blob/master/README.rst) + +name: pre-release + +on: + workflow_dispatch: + push: + +jobs: + default: + strategy: + matrix: + distro: [noetic] + + env: + # https://github.com/ros-industrial/industrial_ci/issues/666 + BUILDER: catkin_make_isolated + ROS_DISTRO: ${{ matrix.distro }} + PRERELEASE: true + BASEDIR: ${{ github.workspace }}/.work + + if: github.event_name == 'workflow_dispatch' # only allow manual triggering + name: "${{ matrix.distro }}" + runs-on: ubuntu-latest + steps: + - name: "Free up disk space" + run: | + sudo apt-get -qq purge build-essential "ghc*" + sudo apt-get clean + # cleanup docker images not used by us + docker system prune -af + # free up a lot of stuff from /usr/local + sudo rm -rf /usr/local + df -h + - uses: actions/checkout@v2 + - name: industrial_ci + uses: ros-industrial/industrial_ci@master diff --git a/.github/workflows/upstream.rosinstall b/.github/workflows/upstream.rosinstall new file mode 100644 index 0000000..ba3c1ee --- /dev/null +++ b/.github/workflows/upstream.rosinstall @@ -0,0 +1,8 @@ +- git: + local-name: redis_store + uri: https://github.com/zultron/redis_store.git + version: noetic-devel +- git: + local-name: redis_store_msgs + uri: https://github.com/zultron/redis_store_msgs.git + version: noetic-devel diff --git a/.github/workflows/upstream_install.sh b/.github/workflows/upstream_install.sh new file mode 100755 index 0000000..51af5e1 --- /dev/null +++ b/.github/workflows/upstream_install.sh @@ -0,0 +1,47 @@ +#!/usr/bin/bash -xe + +# Install tools +apt-get update +apt-get install -y \ + apt-transport-https \ + curl + +# Install Machinekit APT repos +install_cloudsmith_repo() { + BASE=https://dl.cloudsmith.io/public + ORG=$1 + REPO=$2 + KEY_ID=$3 + CLOUDSMITH_ARGS="distro=${ID}&codename=${VERSION_CODENAME}" + curl -1sLf ${BASE}/${ORG}/${REPO}/cfg/gpg/gpg.${KEY_ID}.key | + apt-key add - + curl -1sLf "${BASE}/${ORG}/${REPO}/cfg/setup/config.deb.txt?${CLOUDSMITH_ARGS}" \ + >/etc/apt/sources.list.d/${ORG}-${REPO}.list +} + +source /etc/os-release +install_cloudsmith_repo machinekit machinekit-hal D35981AB4276AC36 +install_cloudsmith_repo machinekit machinekit A9B6D8B4BD8321F3 +apt-get update + +# Add Machinekit rosdep keys +rm -f /etc/ros/rosdep/sources.list.d/20-default.list +rosdep init +UPSTREAM_ROSDEP_YML=/etc/ros/rosdep/upstream-rosdep.yaml +cat > $UPSTREAM_ROSDEP_YML <<-EOF + machinekit: + debian: [machinekit-hal] + ubuntu: [machinekit-hal] + machinekit-dev: + debian: [machinekit-hal-dev] + ubuntu: [machinekit-hal-dev] + python3-redis: + debian: [python3-redis] + ubuntu: [python3-redis] + python3-attrs: + debian: [python3-attr] + ubuntu: [python3-attr] + EOF +echo "yaml file://$UPSTREAM_ROSDEP_YML" > \ + /etc/ros/rosdep/sources.list.d/10-local.list +rosdep update From 65d86426dc5e449c6c66a2ebcaa04d895b3978e9 Mon Sep 17 00:00:00 2001 From: John Morris Date: Mon, 20 Sep 2021 11:57:24 -0500 Subject: [PATCH 15/32] hal_hw_interface: Add `python3-mock` test dep to packaging --- hal_hw_interface/package.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/hal_hw_interface/package.xml b/hal_hw_interface/package.xml index 4838a8e..081e88b 100644 --- a/hal_hw_interface/package.xml +++ b/hal_hw_interface/package.xml @@ -43,6 +43,7 @@ machinekit ros_pytest + python3-mock From 8cbb280649346a1f49cb3a009b0ecd404db52ed7 Mon Sep 17 00:00:00 2001 From: John Morris Date: Mon, 20 Sep 2021 12:08:27 -0500 Subject: [PATCH 16/32] hal_hw_interface: Fix unit tests Tests became stale over time --- .../src/hal_hw_interface/tests/conftest.py | 17 +- .../hal_hw_interface/tests/test_fixtures.py | 182 +++++++++--------- .../tests/test_hal_io_comp.py | 8 +- .../tests/test_redis_store_hal_pin.py | 137 +++++++------ .../tests/test_ros_hal_pin.py | 116 +++++------ 5 files changed, 233 insertions(+), 227 deletions(-) diff --git a/hal_hw_interface/src/hal_hw_interface/tests/conftest.py b/hal_hw_interface/src/hal_hw_interface/tests/conftest.py index ea13020..bbbaa5e 100644 --- a/hal_hw_interface/src/hal_hw_interface/tests/conftest.py +++ b/hal_hw_interface/src/hal_hw_interface/tests/conftest.py @@ -12,23 +12,23 @@ def mock_objs(): @pytest.fixture() -def mock_comp_obj(): +def mock_comp_obj(request): # Mock hal.component and returned object # - Settable and readable pins - pin_value_map = dict(__default=0xDEADBEEF) + request.instance.pin_values = pin_values = dict(__default=0xDEADBEEF) def get_pin(key): - if key in pin_value_map: - value = pin_value_map[key] + if key in pin_values: + value = pin_values[key] print("Returning pin %s value=%s" % (key, value)) else: - value = pin_value_map['__default'] + value = pin_values['__default'] print("Returning pin %s DEFAULT value=0x%x" % (key, value)) return value def set_pin(key, value): print("Setting pin %s value=%s" % (key, value)) - pin_value_map[key] = value + pin_values[key] = value mock_objs_dict['comp_name'] = 'test_comp' comp_getprefix = MagicMock(side_effect=lambda: mock_objs_dict['comp_name']) @@ -140,10 +140,10 @@ def log_side_effect(msg_fmt, *args): @pytest.fixture() -def mock_redis_client_obj(): +def mock_redis_client_obj(request): # Mock redis_store.ConfigClient method and returned object # - Settable and readable pins - key_value_map = dict(__default=0) + request.instance.key_value_map = key_value_map = dict(__default=0) def get_key(key): value = key_value_map.get(key, key_value_map['__default']) @@ -158,6 +158,7 @@ def set_key(key, value): mock_client_obj.configure_mock( name='mock_redis_client_obj', set_key=set_key, # Won't increment mock_calls + on_update_received=list(), **{'get_param.side_effect': get_key, 'set_param.side_effect': set_key} ) diff --git a/hal_hw_interface/src/hal_hw_interface/tests/test_fixtures.py b/hal_hw_interface/src/hal_hw_interface/tests/test_fixtures.py index 63aaf23..1216d4d 100644 --- a/hal_hw_interface/src/hal_hw_interface/tests/test_fixtures.py +++ b/hal_hw_interface/src/hal_hw_interface/tests/test_fixtures.py @@ -7,93 +7,95 @@ keys2 = dict(pin1=False, pin2=1.88e42, pin4=0) -def test_mock_comp_obj_fixture(mock_comp_obj, mock_objs): - # Test hal.component returns mock_comp_obj - assert mock_objs['hal_comp']() is mock_comp_obj - - # Set each pin (with out-of-band method) and check - for name, value in keys1.items(): - mock_comp_obj.set_pin(name, value) - assert mock_comp_obj[name] == value - - # Recheck - for name, value in keys1.items(): - assert mock_comp_obj[name] == value - - # Set each pin and check - for name, value in keys2.items(): - mock_comp_obj[name] = value - assert mock_comp_obj[name] == value - - # Recheck everything - pins = keys1.copy() - pins.update(keys2) - for name, value in pins.items(): - assert mock_comp_obj[name] == value - - # Default case - assert mock_comp_obj['bogus'] == 0xDEADBEEF - - -def test_mock_rospy_fixture(mock_rospy, mock_objs): - # Test mock rospy.get_param() - gp = mock_objs['rospy_get_param'] - gp.set_key('foo', 1) - gp.set_key('bar', 2) - assert gp('foo') == 1 - assert gp('bar') == 2 - gp.set_key('baz', 3) - assert gp('foo') == 1 - assert gp('bar') == 2 - assert gp('baz') == 3 - - # Test rospy.Rate() returns expected object - assert mock_objs['rospy_Rate']() is mock_objs['rospy_Rate_obj'] - - # Test rospy.is_shutdown() returns True values, then False - found_false = False - for i in range(10): - val = mock_objs['rospy_is_shutdown']() - print("iter {} val {}".format(i, val)) - if val is False: - found_false = True - if val is True: - break - else: - raise Exception("is_shutdown never returned True") - if not found_false: - raise Exception("is_shutdown never returned False") - - # Test returned objects - for name in ('Subscriber', 'Publisher', 'Service'): - method = mock_objs['rospy_{}'.format(name)] - obj = mock_objs['rospy_{}_obj'.format(name)] - assert method() == obj - - -def test_mock_redis_client_obj(mock_redis_client_obj, mock_objs): - # Test redis_store.ConfigClient() returns object - assert mock_objs['redis_store']() is mock_redis_client_obj - - # Set each param (with out-of-band method) and check - for name, value in keys1.items(): - mock_redis_client_obj.set_key(name, value) - assert mock_redis_client_obj.get_param(name) == value - - # Recheck - for name, value in keys1.items(): - assert mock_redis_client_obj.get_param(name) == value - - # Set each param and check - for name, value in keys2.items(): - mock_redis_client_obj.set_param(name, value) - assert mock_redis_client_obj.get_param(name) == value - - # Recheck everything - params = keys1.copy() - params.update(keys2) - for name, value in params.items(): - assert mock_redis_client_obj.get_param(name) == value - - # Default case - assert mock_redis_client_obj.get_param('bogus') == 0 +class TestFixtures: + + def test_mock_comp_obj_fixture(self, mock_comp_obj, mock_objs): + # Test hal.component returns mock_comp_obj + assert mock_objs['hal_comp']() is mock_comp_obj + + # Set each pin (with out-of-band method) and check + for name, value in keys1.items(): + mock_comp_obj.set_pin(name, value) + assert mock_comp_obj[name] == value + + # Recheck + for name, value in keys1.items(): + assert mock_comp_obj[name] == value + + # Set each pin and check + for name, value in keys2.items(): + mock_comp_obj[name] = value + assert mock_comp_obj[name] == value + + # Recheck everything + pins = keys1.copy() + pins.update(keys2) + for name, value in pins.items(): + assert mock_comp_obj[name] == value + + # Default case + assert mock_comp_obj['bogus'] == 0xDEADBEEF + + + def test_mock_rospy_fixture(self, mock_rospy, mock_objs): + # Test mock rospy.get_param() + gp = mock_objs['rospy_get_param'] + gp.set_key('foo', 1) + gp.set_key('bar', 2) + assert gp('foo') == 1 + assert gp('bar') == 2 + gp.set_key('baz', 3) + assert gp('foo') == 1 + assert gp('bar') == 2 + assert gp('baz') == 3 + + # Test rospy.Rate() returns expected object + assert mock_objs['rospy_Rate']() is mock_objs['rospy_Rate_obj'] + + # Test rospy.is_shutdown() returns True values, then False + found_false = False + for i in range(10): + val = mock_objs['rospy_is_shutdown']() + print("iter {} val {}".format(i, val)) + if val is False: + found_false = True + if val is True: + break + else: + raise Exception("is_shutdown never returned True") + if not found_false: + raise Exception("is_shutdown never returned False") + + # Test returned objects + for name in ('Subscriber', 'Publisher', 'Service'): + method = mock_objs['rospy_{}'.format(name)] + obj = mock_objs['rospy_{}_obj'.format(name)] + assert method() == obj + + + def test_mock_redis_client_obj(self, mock_redis_client_obj, mock_objs): + # Test redis_store.ConfigClient() returns object + assert mock_objs['redis_store']() is mock_redis_client_obj + + # Set each param (with out-of-band method) and check + for name, value in keys1.items(): + mock_redis_client_obj.set_key(name, value) + assert mock_redis_client_obj.get_param(name) == value + + # Recheck + for name, value in keys1.items(): + assert mock_redis_client_obj.get_param(name) == value + + # Set each param and check + for name, value in keys2.items(): + mock_redis_client_obj.set_param(name, value) + assert mock_redis_client_obj.get_param(name) == value + + # Recheck everything + params = keys1.copy() + params.update(keys2) + for name, value in params.items(): + assert mock_redis_client_obj.get_param(name) == value + + # Default case + assert mock_redis_client_obj.get_param('bogus') == 0 diff --git a/hal_hw_interface/src/hal_hw_interface/tests/test_hal_io_comp.py b/hal_hw_interface/src/hal_hw_interface/tests/test_hal_io_comp.py index eca5a93..7677f0d 100644 --- a/hal_hw_interface/src/hal_hw_interface/tests/test_hal_io_comp.py +++ b/hal_hw_interface/src/hal_hw_interface/tests/test_hal_io_comp.py @@ -76,15 +76,13 @@ def test_hal_io_comp_update(self, obj, pin_params, mock_comp_obj): get_param, test_pins = pin_params obj.setup_component() - # Put pins in dict and set values + # Set pin values for pin in obj.pins: mock_comp_obj.set_pin(pin.name, 1) - pin.last_value = 0 # Run update() and check that pins changed print("----------- Running obj.update()") obj.update() for pin in obj.pins: - print("- pin %s" % pin) - assert pin.last_value == 1 - pin.pub.publish.assert_called_with(1) + print(f"- pin {pin}") + pin.pub.publish.assert_called() diff --git a/hal_hw_interface/src/hal_hw_interface/tests/test_redis_store_hal_pin.py b/hal_hw_interface/src/hal_hw_interface/tests/test_redis_store_hal_pin.py index 30b1fe3..4338047 100644 --- a/hal_hw_interface/src/hal_hw_interface/tests/test_redis_store_hal_pin.py +++ b/hal_hw_interface/src/hal_hw_interface/tests/test_redis_store_hal_pin.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- +import pytest from hal_hw_interface.redis_store_hal_pin import RedisStoreHalPin # Borrow tests from ros_hal_pin @@ -9,6 +10,24 @@ class TestRedisStoreHalPin(TestRosHalPin): default_hal_dir = HalPinDir('OUT') test_class = RedisStoreHalPin + @pytest.fixture(params=TestRosHalPin.obj_cases) + def obj(self, request, mock_comp_obj, mock_rospy, mock_redis_client_obj): + self.setup_hal_obj_base(mock_comp_obj) + mock_comp_obj.setprefix(self.compname) + attrs = dict() + params = request.param.copy() + params.setdefault('hal_dir', self.default_hal_dir) + name = params.pop('name') + attr_names = ['hal_comp', 'hal_type', 'hal_dir', 'msg_type'] + attr_names += self.extra_attrs + for attr_name in attr_names: + if attr_name in params: + attrs[attr_name] = params.pop(attr_name) + obj = self.test_class(name, **attrs) + obj._p = request.param # Send test params in + mock_redis_client_obj.get_param.reset_mock() # Called during setup + return obj + def redis_param_key(self, obj): return "{}/{}".format(obj.compname, obj.pin_name) @@ -20,10 +39,10 @@ def set_mock_redis_param(self, obj, param): obj._redis_config.set_key(self.redis_param_key(obj), value) return value - def test_redis_store_pin_param_key(self, obj): - assert obj._redis_param_key == self.redis_param_key(obj) + def test_redis_store_pin_key(self, obj): + assert obj.key == self.redis_param_key(obj) - def test_redis_store_pin_client(self, mock_redis_client_obj, mock_comp_obj): + def test_redis_store_pin_redis_config(self, mock_redis_client_obj, mock_comp_obj, mock_rospy): # Check that redis client is created and cached self.setup_hal_obj_base(mock_comp_obj) c1 = self.test_class('test_redis_pin', 'FLOAT')._redis_config @@ -31,81 +50,59 @@ def test_redis_store_pin_client(self, mock_redis_client_obj, mock_comp_obj): c2 = self.test_class('test_redis_pin2', 'FLOAT')._redis_config assert c1 is c2 - def test_redis_store_pin_set_pin_from_redis( + def test_redis_store_pin_update_fm_redis(self, obj): + obj._update_fm_redis(obj.key, 42) + print(self.pin_values) + assert self.pin_values[obj.pin_name] == 42 + assert obj._prev_pin_val == 42 + assert obj._prev_redis_val == 42 + # When key doesn't match, no change + obj._update_fm_redis('foo', 13) + assert self.pin_values[obj.pin_name] == 42 + + def test_redis_store_pin_ros_init( self, obj, data, mock_comp_obj, mock_redis_client_obj ): - # Fake a redis param value and set the pin from redis + # obj._redis_config.on_update_received = list() # Clear out setup entry + # Fake a redis param value & run function under test test_val = self.set_mock_redis_param(obj, data) - obj_val = obj.set_pin_from_redis() - # Check that redis was read - mock_redis_client_obj.get_param.assert_called_once_with( - '{}/{}'.format(self.compname, self.obj_test_name(obj)) - ) - # Check the returned value - assert obj_val == test_val - # Check the HAL pin value - mock_comp_obj.__setitem__.assert_called_with( - self.obj_test_name(obj), test_val - ) - - def test_redis_store_pin_set_pin_no_defaults(self, all_patches): - ( - mock_comp_obj, - mock_rospy, - mock_objs, - mock_redis_client_obj, - ) = all_patches - # Fake a redis param value and set the pin from redis - obj = self.test_class('unconfigured_redis_pin', 'FLOAT') - self.setup_hal_obj_base(mock_comp_obj) - self.set_mock_redis_param(obj, None) - mock_redis_client_obj.reset_mock() - # Call set_pin_from_redis() and check that the pin was NOT set - print("--------- Calling method") - obj.set_pin_from_redis() - print(mock_comp_obj.__setitem__.mock_calls) - mock_comp_obj.__setitem__.assert_not_called() + self.set_pin(obj, data['pin_value']) + obj._ros_init() + + if str(obj.hal_dir) != 'HAL_IN': + # Check that redis was read + print('get_param calls:', mock_redis_client_obj.get_param.mock_calls) + mock_redis_client_obj.get_param.assert_called_once_with(obj.key) + # Check the returned value + assert obj._prev_pin_val == test_val + assert obj._prev_redis_val == test_val + assert self.pin_values[obj.pin_name] == test_val + # Check the callback list + assert obj._redis_config.on_update_received[-1] == obj._update_fm_redis + + else: # HAL_IN pins + assert obj._prev_pin_val == self.pin_values[obj.pin_name] + assert obj._prev_redis_val is None def test_redis_store_pin_set_redis_from_pin( self, obj, data, mock_redis_client_obj ): - # Fake a HAL pin value, set redis key, clean up + # Fake values test_val = self.set_pin(obj, data) - self.set_mock_redis_param(obj, data) - mock_redis_client_obj.reset_mock() + last_val = self.set_last_value(obj, data) + obj._prev_pin_val = obj._prev_redis_val = last_val + # Call the method print("--------- Calling method") - obj_val = obj.set_redis_from_pin() - # Check the returned value - assert obj_val == test_val - # Check redis was written to - mock_redis_client_obj.set_param.assert_called_once_with( - '{}/{}'.format(self.compname, self.obj_test_name(obj)), test_val - ) - # Check if log message was printed - print(mock_redis_client_obj.get_param.mock_calls) - if data['changed']: - # Call get_param to check value and again to print log - assert mock_redis_client_obj.get_param.call_count == 2 - else: - # Only call get_param once to check value, not again to print log - assert mock_redis_client_obj.get_param.call_count == 1 + obj.update() - def test_redis_store_pin_set_redis_from_pin_first_time( - self, obj, data, mock_redis_client_obj - ): - # Fake a HAL pin value, set redis key to None, clean up - test_val = self.set_pin(obj, data) - self.set_mock_redis_param(obj, None) - mock_redis_client_obj.reset_mock() - # Call the method - obj_val = obj.set_redis_from_pin() - # Check the returned value - assert obj_val == test_val - # Check redis was written to - mock_redis_client_obj.set_param.assert_called_once_with( - '{}/{}'.format(self.compname, self.obj_test_name(obj)), test_val - ) - # Check the log message was not printed - print(mock_redis_client_obj.get_param.mock_calls) - assert mock_redis_client_obj.get_param.call_count == 1 + if str(obj.hal_dir) == 'HAL_OUT': + obj._redis_config.set_param.assert_not_called() + return + + if test_val == last_val: + return + + # Check the new value + assert obj._prev_pin_val == test_val + assert obj._prev_redis_val == test_val diff --git a/hal_hw_interface/src/hal_hw_interface/tests/test_ros_hal_pin.py b/hal_hw_interface/src/hal_hw_interface/tests/test_ros_hal_pin.py index bd6b879..b300cf2 100644 --- a/hal_hw_interface/src/hal_hw_interface/tests/test_ros_hal_pin.py +++ b/hal_hw_interface/src/hal_hw_interface/tests/test_ros_hal_pin.py @@ -21,55 +21,6 @@ ) from std_msgs.msg import UInt16 # For invalid test case -# List of object fixture parameter test cases to run for each class; -# contains enough attributes for superset of all classes -obj_cases = [ - dict( # 0 Test BIT, IO, names - name='reset', - hal_type='BIT', - hal_dir='IO', - sub_topic='/robot/reset', - pub_topic='/robot/reset', - service_name='/robot/reset', - ), - dict(name='bool', hal_type='BIT', hal_dir='OUT'), # 1 Test BIT OUT - dict(name='bool', hal_type='BIT'), # 2 Test BIT defaults - dict(name='u32_out', hal_type='U32', hal_dir='OUT'), # 3 Test U32 OUT - dict(name='u32_out', hal_type='U32', hal_dir='IO'), # 4 Test U32 IN - dict(name='u32_out', hal_type='U32'), # 5 Test U32 default - dict(name='s32_out', hal_type='S32'), # 6 Test S32 default - dict(name='float_io', hal_type='FLOAT', hal_dir='IO'), # 7 Test FLOAT IO -] - -# Message and pin data cases -# Values are (bit, u32, s32, float) -data_cases = [ - dict( # 0 Test random - pin_value=(False, 23, -4, 1e-9), other_value=(True, 27, 49, 3.98) - ), - dict( # 1 Test random - pin_value=(True, 0, -39958, 54.7), - other_value=(False, 199, -33399, 149.33285), - ), - dict( # 2 Test zeros -> random - pin_value=(False, 0, 0, 0.0), - other_value=(False, 0, 0, 0.0), - changed=False, - ), - dict( # 3 Test data unchanged - pin_value=(True, 199, -33399, 149.33285), - other_value=(True, 199, -33399, 149.33285), - changed=False, - ), -] - -isclose_cases = [ - (0.0, 1.0, False), - (100, 10, False), - (0.0, 0.0, True), - (1e-9, 1.5e-9, True), -] - class TestRosHalPin(object): test_class = RosHalPin @@ -81,12 +32,34 @@ class TestRosHalPin(object): # # Object and data fixtures # + + # List of object fixture parameter test cases to run for each class; + # contains enough attributes for superset of all classes + obj_cases = [ + dict( # 0 Test BIT, IO, names + name='reset', + hal_type='BIT', + hal_dir='IO', + sub_topic='/robot/reset', + pub_topic='/robot/reset', + service_name='/robot/reset', + ), + dict(name='bool', hal_type='BIT', hal_dir='OUT'), # 1 Test BIT OUT + dict(name='bool', hal_type='BIT'), # 2 Test BIT defaults + dict(name='u32_out', hal_type='U32', hal_dir='OUT'), # 3 Test U32 OUT + dict(name='u32_out', hal_type='U32', hal_dir='IO'), # 4 Test U32 IN + dict(name='u32_out', hal_type='U32'), # 5 Test U32 default + dict(name='s32_out', hal_type='S32'), # 6 Test S32 default + dict(name='float_io', hal_type='FLOAT', hal_dir='IO'), # 7 Test FLOAT IO + ] + @pytest.fixture(params=obj_cases) def obj(self, request, mock_comp_obj, mock_rospy, mock_redis_client_obj): self.setup_hal_obj_base(mock_comp_obj) mock_comp_obj.setprefix(self.compname) attrs = dict() params = request.param.copy() + params.setdefault('hal_dir', self.default_hal_dir) name = params.pop('name') attr_names = ['hal_comp', 'hal_type', 'hal_dir', 'msg_type'] attr_names += self.extra_attrs @@ -97,12 +70,40 @@ def obj(self, request, mock_comp_obj, mock_rospy, mock_redis_client_obj): obj._p = request.param # Send test params in return obj + # Message and pin data cases + # Values are (bit, u32, s32, float) + data_cases = [ + dict( # 0 Test random + pin_value=(False, 23, -4, 1e-9), other_value=(True, 27, 49, 3.98) + ), + dict( # 1 Test random + pin_value=(True, 0, -39958, 54.7), + other_value=(False, 199, -33399, 149.33285), + ), + dict( # 2 Test zeros -> random + pin_value=(False, 0, 0, 0.0), + other_value=(False, 0, 0, 0.0), + changed=False, + ), + dict( # 3 Test data unchanged + pin_value=(True, 199, -33399, 149.33285), + other_value=(True, 199, -33399, 149.33285), + changed=False, + ), + ] + @pytest.fixture(params=data_cases) def data(self, request): - if 'changed' not in request.param: - request.param['changed'] = True + request.param.setdefault('changed', True) return request.param + isclose_cases = [ + (0.0, 1.0, False), + (100, 10, False), + (0.0, 0.0, True), + (1e-9, 1.5e-9, True), + ] + @pytest.fixture(params=isclose_cases) def isclose_case(self, request): return request.param @@ -184,8 +185,7 @@ def test_ros_hal_pin_compname(self, mock_comp_obj, all_patches): print(mock_comp_obj.mock_calls) assert mock_comp_obj.getprefix.call_count == 1 - def test_ros_hal_pin_obj_fixture(self, obj, all_patches): - mock_comp_obj, mock_rospy, mock_objs = all_patches[:3] + def test_ros_hal_pin_obj_fixture(self, obj, mock_comp_obj, mock_rospy, mock_objs): assert hasattr(obj, '_p') params = obj._p assert obj.name == params['name'] @@ -268,6 +268,12 @@ class TestRosHalPinPublisher(TestRosHalPin): def pub_topic(self, obj): return self.get_obj_test_param(obj, 'pub_topic', self.ros_name(obj)) + def set_last_value(self, obj, param): + # Set msg.data + value = super().set_last_value(obj, param) + obj._msg.data = value + return value + # # Tests # @@ -301,8 +307,10 @@ def test_ros_hal_pin_publisher_update(self, obj, data): self.set_last_value(obj, data) cur_value = self.set_pin(obj, data) obj.update() + assert obj._msg.data == cur_value + print(f'pub.publish calls: {obj.pub.publish.mock_calls}') if data.get('changed'): - obj.pub.publish.assert_called_with(cur_value) + obj.pub.publish.assert_called_with(obj._msg) else: obj.pub.publish.assert_not_called From 8050fccb059fe6508d77cd86f0c1b3af8824d025 Mon Sep 17 00:00:00 2001 From: John Morris Date: Mon, 20 Sep 2021 17:04:05 -0500 Subject: [PATCH 17/32] Fix `catkin lint` errors --- hal_hw_interface/package.xml | 9 +++++++-- hal_rrbot_control/package.xml | 4 ++++ 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/hal_hw_interface/package.xml b/hal_hw_interface/package.xml index 081e88b..b5bfb7f 100644 --- a/hal_hw_interface/package.xml +++ b/hal_hw_interface/package.xml @@ -24,10 +24,13 @@ message_generation python3-catkin-pkg redis_store - roslaunch machinekit-dev - + ros_control_boilerplate + controller_manager + roscpp + rospy + redis_store machinekit-dev ros_control_boilerplate @@ -42,8 +45,10 @@ rosbash machinekit + rostest ros_pytest python3-mock + roslaunch diff --git a/hal_rrbot_control/package.xml b/hal_rrbot_control/package.xml index f989225..bd708b0 100644 --- a/hal_rrbot_control/package.xml +++ b/hal_rrbot_control/package.xml @@ -15,6 +15,10 @@ hal_hw_interface hal_hw_interface + xacro + controller_manager + robot_state_publisher + rviz From d3f8bcbc4a5b87c25b58b1d3ca11213a676b9182 Mon Sep 17 00:00:00 2001 From: John Morris Date: Mon, 20 Sep 2021 17:06:36 -0500 Subject: [PATCH 18/32] Fix CI on Melodic $ ( .github/workflows/upstream_install.sh; ) /home/runner/work/_actions/ros-industrial/industrial_ci/master/industrial_ci/src/util.sh: .github/workflows/upstream_install.sh: /usr/bin/bash: bad interpreter: No such file or directory 'before_setup_upstream_workspace' returned with code '126' after 0 min 0 sec Error: Process completed with exit code 126. --- .github/workflows/upstream_install.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/upstream_install.sh b/.github/workflows/upstream_install.sh index 51af5e1..6235678 100755 --- a/.github/workflows/upstream_install.sh +++ b/.github/workflows/upstream_install.sh @@ -1,4 +1,4 @@ -#!/usr/bin/bash -xe +#!/bin/bash -xe # Install tools apt-get update From f7253df718872ff6483fb48dbbcfeb7097780cb4 Mon Sep 17 00:00:00 2001 From: John Morris Date: Mon, 20 Sep 2021 17:09:44 -0500 Subject: [PATCH 19/32] Add changes from pre-commit hooks --- .github/workflows/catkin_tools_devel.sh | 8 +++++--- .github/workflows/upstream_install.sh | 2 +- .../src/hal_hw_interface/tests/test_fixtures.py | 3 --- .../tests/test_redis_store_hal_pin.py | 12 +++++++++--- .../src/hal_hw_interface/tests/test_ros_hal_pin.py | 8 ++++++-- 5 files changed, 21 insertions(+), 12 deletions(-) diff --git a/.github/workflows/catkin_tools_devel.sh b/.github/workflows/catkin_tools_devel.sh index fa1c4f5..9467a6a 100644 --- a/.github/workflows/catkin_tools_devel.sh +++ b/.github/workflows/catkin_tools_devel.sh @@ -5,11 +5,13 @@ BUILDER=catkin_tools ici_source_builder function ici_extend_space { - echo "$1/devel" + echo "$1/devel" } function _catkin_config { - local extend=$1; shift - local ws=$1; shift + local extend=$1 + shift + local ws=$1 + shift ici_exec_in_workspace "$extend" "$ws" catkin config --init } diff --git a/.github/workflows/upstream_install.sh b/.github/workflows/upstream_install.sh index 6235678..b292382 100755 --- a/.github/workflows/upstream_install.sh +++ b/.github/workflows/upstream_install.sh @@ -28,7 +28,7 @@ apt-get update rm -f /etc/ros/rosdep/sources.list.d/20-default.list rosdep init UPSTREAM_ROSDEP_YML=/etc/ros/rosdep/upstream-rosdep.yaml -cat > $UPSTREAM_ROSDEP_YML <<-EOF +cat >$UPSTREAM_ROSDEP_YML <<-EOF machinekit: debian: [machinekit-hal] ubuntu: [machinekit-hal] diff --git a/hal_hw_interface/src/hal_hw_interface/tests/test_fixtures.py b/hal_hw_interface/src/hal_hw_interface/tests/test_fixtures.py index 1216d4d..3d853af 100644 --- a/hal_hw_interface/src/hal_hw_interface/tests/test_fixtures.py +++ b/hal_hw_interface/src/hal_hw_interface/tests/test_fixtures.py @@ -8,7 +8,6 @@ class TestFixtures: - def test_mock_comp_obj_fixture(self, mock_comp_obj, mock_objs): # Test hal.component returns mock_comp_obj assert mock_objs['hal_comp']() is mock_comp_obj @@ -36,7 +35,6 @@ def test_mock_comp_obj_fixture(self, mock_comp_obj, mock_objs): # Default case assert mock_comp_obj['bogus'] == 0xDEADBEEF - def test_mock_rospy_fixture(self, mock_rospy, mock_objs): # Test mock rospy.get_param() gp = mock_objs['rospy_get_param'] @@ -72,7 +70,6 @@ def test_mock_rospy_fixture(self, mock_rospy, mock_objs): obj = mock_objs['rospy_{}_obj'.format(name)] assert method() == obj - def test_mock_redis_client_obj(self, mock_redis_client_obj, mock_objs): # Test redis_store.ConfigClient() returns object assert mock_objs['redis_store']() is mock_redis_client_obj diff --git a/hal_hw_interface/src/hal_hw_interface/tests/test_redis_store_hal_pin.py b/hal_hw_interface/src/hal_hw_interface/tests/test_redis_store_hal_pin.py index 4338047..fea24f4 100644 --- a/hal_hw_interface/src/hal_hw_interface/tests/test_redis_store_hal_pin.py +++ b/hal_hw_interface/src/hal_hw_interface/tests/test_redis_store_hal_pin.py @@ -42,7 +42,9 @@ def set_mock_redis_param(self, obj, param): def test_redis_store_pin_key(self, obj): assert obj.key == self.redis_param_key(obj) - def test_redis_store_pin_redis_config(self, mock_redis_client_obj, mock_comp_obj, mock_rospy): + def test_redis_store_pin_redis_config( + self, mock_redis_client_obj, mock_comp_obj, mock_rospy + ): # Check that redis client is created and cached self.setup_hal_obj_base(mock_comp_obj) c1 = self.test_class('test_redis_pin', 'FLOAT')._redis_config @@ -71,14 +73,18 @@ def test_redis_store_pin_ros_init( if str(obj.hal_dir) != 'HAL_IN': # Check that redis was read - print('get_param calls:', mock_redis_client_obj.get_param.mock_calls) + print( + 'get_param calls:', mock_redis_client_obj.get_param.mock_calls + ) mock_redis_client_obj.get_param.assert_called_once_with(obj.key) # Check the returned value assert obj._prev_pin_val == test_val assert obj._prev_redis_val == test_val assert self.pin_values[obj.pin_name] == test_val # Check the callback list - assert obj._redis_config.on_update_received[-1] == obj._update_fm_redis + assert ( + obj._redis_config.on_update_received[-1] == obj._update_fm_redis + ) else: # HAL_IN pins assert obj._prev_pin_val == self.pin_values[obj.pin_name] diff --git a/hal_hw_interface/src/hal_hw_interface/tests/test_ros_hal_pin.py b/hal_hw_interface/src/hal_hw_interface/tests/test_ros_hal_pin.py index b300cf2..be5170f 100644 --- a/hal_hw_interface/src/hal_hw_interface/tests/test_ros_hal_pin.py +++ b/hal_hw_interface/src/hal_hw_interface/tests/test_ros_hal_pin.py @@ -50,7 +50,9 @@ class TestRosHalPin(object): dict(name='u32_out', hal_type='U32', hal_dir='IO'), # 4 Test U32 IN dict(name='u32_out', hal_type='U32'), # 5 Test U32 default dict(name='s32_out', hal_type='S32'), # 6 Test S32 default - dict(name='float_io', hal_type='FLOAT', hal_dir='IO'), # 7 Test FLOAT IO + dict( + name='float_io', hal_type='FLOAT', hal_dir='IO' + ), # 7 Test FLOAT IO ] @pytest.fixture(params=obj_cases) @@ -185,7 +187,9 @@ def test_ros_hal_pin_compname(self, mock_comp_obj, all_patches): print(mock_comp_obj.mock_calls) assert mock_comp_obj.getprefix.call_count == 1 - def test_ros_hal_pin_obj_fixture(self, obj, mock_comp_obj, mock_rospy, mock_objs): + def test_ros_hal_pin_obj_fixture( + self, obj, mock_comp_obj, mock_rospy, mock_objs + ): assert hasattr(obj, '_p') params = obj._p assert obj.name == params['name'] From 91cad7c3ef352374388ab154681eefbb8bef157e Mon Sep 17 00:00:00 2001 From: John Morris Date: Mon, 20 Sep 2021 17:43:54 -0500 Subject: [PATCH 20/32] Update formatter rules --- .flake8 | 11 ++--------- .pre-commit-config.yaml | 2 +- 2 files changed, 3 insertions(+), 10 deletions(-) diff --git a/.flake8 b/.flake8 index 03dbaad..acb10fb 100644 --- a/.flake8 +++ b/.flake8 @@ -1,12 +1,5 @@ [flake8] -# E501: line-length checking, handled by black # E203: space after :, not PEP8 compliant # W503: no operator after line break, not PEP8 compliant -ignore = E501, E203, W503 -exclude = - # ignore templates - src/robot_ui/src/pathpilot/robot/program/templates/* - # ignore example programs - src/robot_command/examples/programs/* - # ignore playground files - src/robot_command/playground/* +ignore = E203, W503 +max-line-length = 80 diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 37c4e2f..4f11d2f 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -21,7 +21,7 @@ repos: description: This hook formats Python code. entry: env LC_ALL=C.UTF-8 black -q language: system - args: [-S, -l, "80"] + args: [-l, "80"] types: [python] - id: clang-format From 141853c403d553f601ed7295d7ba225f731cc71d Mon Sep 17 00:00:00 2001 From: John Morris Date: Mon, 20 Sep 2021 17:44:13 -0500 Subject: [PATCH 21/32] Commit changes after formatter rule adjustment --- hal_hw_interface/doc/conf.py | 62 ++++---- hal_hw_interface/scripts/hal_io | 2 +- hal_hw_interface/scripts/hal_mgr | 8 +- hal_hw_interface/scripts/hal_offset_mgr | 2 +- hal_hw_interface/setup.py | 2 +- .../src/hal_hw_interface/__init__.py | 2 +- .../src/hal_hw_interface/hal_io_comp.py | 5 +- .../src/hal_hw_interface/hal_mgr.py | 34 ++--- .../src/hal_hw_interface/hal_obj_base.py | 18 +-- .../src/hal_hw_interface/hal_pin_attrs.py | 13 +- .../src/hal_hw_interface/loadrt_local.py | 8 +- .../hal_hw_interface/redis_store_hal_pin.py | 14 +- .../src/hal_hw_interface/ros_hal_component.py | 4 +- .../src/hal_hw_interface/ros_hal_pin.py | 43 +++--- .../src/hal_hw_interface/tests/conftest.py | 50 +++---- .../hal_hw_interface/tests/test_fixtures.py | 36 ++--- .../tests/test_hal_io_comp.py | 38 ++--- .../tests/test_hal_obj_base.py | 20 +-- .../tests/test_hal_pin_attrs.py | 30 ++-- .../tests/test_redis_store_hal_pin.py | 22 +-- .../tests/test_ros_hal_component.py | 32 ++--- .../tests/test_ros_hal_pin.py | 134 +++++++++--------- 22 files changed, 291 insertions(+), 288 deletions(-) diff --git a/hal_hw_interface/doc/conf.py b/hal_hw_interface/doc/conf.py index c520ab4..0b99d8f 100644 --- a/hal_hw_interface/doc/conf.py +++ b/hal_hw_interface/doc/conf.py @@ -39,36 +39,36 @@ # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. extensions = [ - 'sphinx.ext.autodoc', - 'sphinx.ext.doctest', - 'sphinx.ext.intersphinx', - 'sphinx.ext.todo', - 'sphinx.ext.coverage', - 'sphinx.ext.viewcode', - 'sphinx.ext.githubpages', - 'sphinx.ext.inheritance_diagram', + "sphinx.ext.autodoc", + "sphinx.ext.doctest", + "sphinx.ext.intersphinx", + "sphinx.ext.todo", + "sphinx.ext.coverage", + "sphinx.ext.viewcode", + "sphinx.ext.githubpages", + "sphinx.ext.inheritance_diagram", ] # Add any paths that contain templates here, relative to this directory. -templates_path = ['.templates'] +templates_path = [".templates"] # The suffix(es) of source filenames. # You can specify multiple suffix as a list of string: # # source_suffix = ['.rst', '.md'] -source_suffix = '.rst' +source_suffix = ".rst" # The encoding of source files. # # source_encoding = 'utf-8-sig' # The master toctree document. -master_doc = 'index' +master_doc = "index" # General information about the project. -project = u'hal_hw_interface' -copyright = u'2019, John Morris' -author = u'John Morris' +project = u"hal_hw_interface" +copyright = u"2019, John Morris" +author = u"John Morris" # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the @@ -101,7 +101,7 @@ # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. # This patterns also effect to html_static_path and html_extra_path -exclude_patterns = ['.build', 'Thumbs.db', '.DS_Store'] +exclude_patterns = [".build", "Thumbs.db", ".DS_Store"] # The reST default role (used for this markup: `text`) to use for all # documents. @@ -123,7 +123,7 @@ # show_authors = False # The name of the Pygments (syntax highlighting) style to use. -pygments_style = 'sphinx' +pygments_style = "sphinx" # A list of ignored prefixes for module index sorting. # modindex_common_prefix = [] @@ -142,7 +142,7 @@ # # Added according to http://wiki.ros.org/Sphinx # html_theme = 'alabaster' -html_theme = 'agogo' +html_theme = "agogo" # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. @@ -255,7 +255,7 @@ # html_search_scorer = 'scorer.js' # Output file base name for HTML help builder. -htmlhelp_basename = 'hal_hw_interfacedoc' +htmlhelp_basename = "hal_hw_interfacedoc" # -- Options for LaTeX output --------------------------------------------- @@ -280,10 +280,10 @@ latex_documents = [ ( master_doc, - 'hal_hw_interface.tex', - u'hal\\_hw\\_interface Documentation', - u'John Morris', - 'manual', + "hal_hw_interface.tex", + u"hal\\_hw\\_interface Documentation", + u"John Morris", + "manual", ) ] @@ -327,8 +327,8 @@ man_pages = [ ( master_doc, - 'hal_hw_interface', - u'hal_hw_interface Documentation', + "hal_hw_interface", + u"hal_hw_interface Documentation", [author], 1, ) @@ -347,12 +347,12 @@ texinfo_documents = [ ( master_doc, - 'hal_hw_interface', - u'hal_hw_interface Documentation', + "hal_hw_interface", + u"hal_hw_interface Documentation", author, - 'hal_hw_interface', - 'One line description of project.', - 'Miscellaneous', + "hal_hw_interface", + "One line description of project.", + "Miscellaneous", ) ] @@ -374,9 +374,9 @@ # Example configuration for intersphinx: refer to the Python standard library. -intersphinx_mapping = {'https://docs.python.org/': None} +intersphinx_mapping = {"https://docs.python.org/": None} # -- Options for inheritance graphs --------------------------------------- -inheritance_graph_attrs = dict(rankdir='TB') +inheritance_graph_attrs = dict(rankdir="TB") diff --git a/hal_hw_interface/scripts/hal_io b/hal_hw_interface/scripts/hal_io index 7c026ba..d68b01c 100755 --- a/hal_hw_interface/scripts/hal_io +++ b/hal_hw_interface/scripts/hal_io @@ -34,5 +34,5 @@ from hal_hw_interface.hal_io_comp import HalIO -if __name__ == '__main__': +if __name__ == "__main__": HalIO().main() diff --git a/hal_hw_interface/scripts/hal_mgr b/hal_hw_interface/scripts/hal_mgr index 59665eb..970c70c 100755 --- a/hal_hw_interface/scripts/hal_mgr +++ b/hal_hw_interface/scripts/hal_mgr @@ -36,12 +36,12 @@ import os from hal_hw_interface import hal_mgr -MAIN_HAL = 'main.py' -NAME = 'hal_mgr' -HAL_IO = 'hal_io' +MAIN_HAL = "main.py" +NAME = "hal_mgr" +HAL_IO = "hal_io" SCRIPT_DIR = os.path.dirname(os.path.realpath(__file__)) -if __name__ == '__main__': +if __name__ == "__main__": os.chdir(SCRIPT_DIR) hal_mgr.main() diff --git a/hal_hw_interface/scripts/hal_offset_mgr b/hal_hw_interface/scripts/hal_offset_mgr index 59eda3b..3580e2f 100755 --- a/hal_hw_interface/scripts/hal_offset_mgr +++ b/hal_hw_interface/scripts/hal_offset_mgr @@ -3,5 +3,5 @@ from hal_hw_interface.hal_offset_mgr import HalOffsetMgr -if __name__ == '__main__': +if __name__ == "__main__": HalOffsetMgr().main() diff --git a/hal_hw_interface/setup.py b/hal_hw_interface/setup.py index ad564eb..8df979e 100644 --- a/hal_hw_interface/setup.py +++ b/hal_hw_interface/setup.py @@ -5,7 +5,7 @@ from catkin_pkg.python_setup import generate_distutils_setup d = generate_distutils_setup( - packages=['hal_hw_interface'], package_dir={'': 'src'} + packages=["hal_hw_interface"], package_dir={"": "src"} ) setup(**d) diff --git a/hal_hw_interface/src/hal_hw_interface/__init__.py b/hal_hw_interface/src/hal_hw_interface/__init__.py index 9bb2a97..70c7462 100644 --- a/hal_hw_interface/src/hal_hw_interface/__init__.py +++ b/hal_hw_interface/src/hal_hw_interface/__init__.py @@ -5,6 +5,6 @@ """ -__all__ = ('loadrt_local', 'hal', 'rtapi') +__all__ = ("loadrt_local", "hal", "rtapi") from .loadrt_local import loadrt_local, hal, rtapi diff --git a/hal_hw_interface/src/hal_hw_interface/hal_io_comp.py b/hal_hw_interface/src/hal_hw_interface/hal_io_comp.py index f8aac52..e99de9a 100644 --- a/hal_hw_interface/src/hal_hw_interface/hal_io_comp.py +++ b/hal_hw_interface/src/hal_hw_interface/hal_io_comp.py @@ -44,10 +44,11 @@ class HalIO(RosHalComponent): :py:func:`update` functions, if any. """ - compname = 'hal_io' + compname = "hal_io" def setup_component(self): - """Load pin configuration from ROS param server and create pin objects""" + """Load pin configuration from ROS param server and create pin + objects""" self.pins = [] pin_class_map = dict( subscribe_pins=RosHalPinSubscriber, diff --git a/hal_hw_interface/src/hal_hw_interface/hal_mgr.py b/hal_hw_interface/src/hal_hw_interface/hal_mgr.py index c84bc99..a695abc 100644 --- a/hal_hw_interface/src/hal_hw_interface/hal_mgr.py +++ b/hal_hw_interface/src/hal_hw_interface/hal_mgr.py @@ -12,7 +12,7 @@ class HalMgr(object): - NAME = 'hal_mgr' + NAME = "hal_mgr" READY_TOPIC = "hal_mgr/ready" shutdown_begun = False @@ -21,7 +21,7 @@ def __init__(self): rospy.init_node(self.NAME) # Call end_session() on ROS shutdown; don't have the launcher # register its own exit handler - rospy.on_shutdown(lambda: self.shutdown('Graceful shutdown via ROS')) + rospy.on_shutdown(lambda: self.shutdown("Graceful shutdown via ROS")) self._rate = rospy.Rate(1) # 1hz rospy.loginfo("hal_mgr: Initialized node") @@ -30,16 +30,16 @@ def __init__(self): ) def start(self): - # Find the hal_hw_interface comp's directory in LD_LIBRARY_PATH and put it - # into $COMP_DIR - for path in os.environ.get('LD_LIBRARY_PATH', '').split(':'): + # Find the hal_hw_interface comp's directory in + # LD_LIBRARY_PATH and put it into $COMP_DIR + for path in os.environ.get("LD_LIBRARY_PATH", "").split(":"): rospy.loginfo(f"Checking for hal_hw_interface.so in {path}") - if os.path.exists(os.path.join(path, 'hal_hw_interface.so')): + if os.path.exists(os.path.join(path, "hal_hw_interface.so")): comp_dir = path break else: comp_dir = "" - os.environ['COMP_DIR'] = comp_dir + os.environ["COMP_DIR"] = comp_dir rospy.loginfo("hal_mgr: COMP_DIR set to '%s'" % comp_dir) # Get parameters @@ -51,9 +51,9 @@ def start(self): self.shutdown("No keys defined at %s" % self.NAME, 1) return - if 'hal_files' not in hal_mgr_config: + if "hal_files" not in hal_mgr_config: self.shutdown("%s has no 'hal_files' key" % self.NAME, 1) - if 'hal_file_dir' not in hal_mgr_config: + if "hal_file_dir" not in hal_mgr_config: self.shutdown("%s has no 'hal_file_dir' key" % self.NAME, 1) # Set up HAL @@ -62,22 +62,22 @@ def start(self): rospy.loginfo("hal_mgr: Started realtime") # Load rtapi module and set up signal handlers - if not getattr(rtapi, '__rtapicmd'): + if not getattr(rtapi, "__rtapicmd"): rtapi.init_RTAPI() def shutdown_graceful(signum, frame): - self.shutdown('Gracefully shutting down after interrupt signal') + self.shutdown("Gracefully shutting down after interrupt signal") signal.signal(signal.SIGINT, shutdown_graceful) signal.signal(signal.SIGTERM, shutdown_graceful) # Load HAL configuration - for fname in hal_mgr_config['hal_files']: - fpath = os.path.join(hal_mgr_config['hal_file_dir'], fname) + for fname in hal_mgr_config["hal_files"]: + fpath = os.path.join(hal_mgr_config["hal_file_dir"], fname) if not os.path.exists(fpath): self.shutdown( "No file '%s' in directory '%s'" - % (fname, hal_mgr_config['hal_file_dir']) + % (fname, hal_mgr_config["hal_file_dir"]) ) rospy.loginfo("hal_mgr: Loading hal file '%s'" % fname) launcher.load_hal_file(fpath) @@ -109,12 +109,12 @@ def shutdown(self, msg="Shutting down for unknown reason", res=0): def main(): - debug = int(os.environ.get('DEBUG', 0)) + debug = int(os.environ.get("DEBUG", 0)) launcher.set_debug_level(debug) - if 'MACHINEKIT_INI' not in os.environ: # export for package installs + if "MACHINEKIT_INI" not in os.environ: # export for package installs mkconfig = config.Config() - os.environ['MACHINEKIT_INI'] = mkconfig.MACHINEKIT_INI + os.environ["MACHINEKIT_INI"] = mkconfig.MACHINEKIT_INI hal_mgr = HalMgr() try: diff --git a/hal_hw_interface/src/hal_hw_interface/hal_obj_base.py b/hal_hw_interface/src/hal_hw_interface/hal_obj_base.py index 1875e0b..04cdd47 100644 --- a/hal_hw_interface/src/hal_hw_interface/hal_obj_base.py +++ b/hal_hw_interface/src/hal_hw_interface/hal_obj_base.py @@ -31,11 +31,11 @@ def init_hal_comp(self): :py:class:`hal_hw_interface.ros_hal_pin.RosHalComponent` object setup to initialize the new HAL component """ - if not hasattr(self, 'compname'): + if not hasattr(self, "compname"): raise RuntimeError('No "compname" attribute configured') - if 'hal_comp' in self._cached_objs: - raise RuntimeError('HAL component already initialized') - self._cached_objs['hal_comp'] = hal.component(self.compname) + if "hal_comp" in self._cached_objs: + raise RuntimeError("HAL component already initialized") + self._cached_objs["hal_comp"] = hal.component(self.compname) @property def hal_comp(self): @@ -47,9 +47,9 @@ def hal_comp(self): :returns: HAL component object :rtype: :py:class:`hal.component` """ - if 'hal_comp' not in self._cached_objs: - raise RuntimeError('No HAL component initialized') - return self._cached_objs['hal_comp'] + if "hal_comp" not in self._cached_objs: + raise RuntimeError("No HAL component initialized") + return self._cached_objs["hal_comp"] def get_ros_param(self, suffix, default=None): """Retrieve a parameter from the ROS param server, key name prefixed @@ -67,7 +67,7 @@ def get_ros_param(self, suffix, default=None): """ return self._cached_objs.setdefault( suffix, - rospy.get_param('{}/{}'.format(self.compname, suffix), default), + rospy.get_param("{}/{}".format(self.compname, suffix), default), ) def add_shutdown_callback(self, cb): @@ -78,4 +78,4 @@ def add_shutdown_callback(self, cb): :py:func:`hal_hw_interface.ros_hal_component.RosHalComponent.shutdown_component` at component shut down time. """ - self._cached_objs.setdefault('shutdown_cbs', []).append(cb) + self._cached_objs.setdefault("shutdown_cbs", []).append(cb) diff --git a/hal_hw_interface/src/hal_hw_interface/hal_pin_attrs.py b/hal_hw_interface/src/hal_hw_interface/hal_pin_attrs.py index 125321d..126eef5 100644 --- a/hal_hw_interface/src/hal_hw_interface/hal_pin_attrs.py +++ b/hal_hw_interface/src/hal_hw_interface/hal_pin_attrs.py @@ -14,7 +14,7 @@ class HalPinAttrBase(int): _fwd_map = dict() # 16 -> 'IO' _bwd_map = dict() # 'HAL_IN' -> 16; 'IN' -> 16 for attr in dir(hal): - if not attr.startswith('HAL_'): + if not attr.startswith("HAL_"): continue value = getattr(hal, attr) attr_short = attr[4:] @@ -22,13 +22,14 @@ class HalPinAttrBase(int): _bwd_map[attr_short] = value def __new__(cls, value): - """Create new object, translating strings to ints and validating value""" + """Create new object, translating strings to ints and validating + value""" if isinstance(value, int): if cls._fwd_map.get(value, None) not in cls._suffixes: raise ValueError("Illegal value '{}'".format(value)) return int.__new__(cls, value) elif isinstance(value, str): - if value.startswith('HAL_'): + if value.startswith("HAL_"): value = value[4:] if value not in cls._suffixes: raise ValueError("Illegal value '{}'".format(value)) @@ -37,7 +38,7 @@ def __new__(cls, value): raise ValueError("Illegal value '{}'".format(value)) def __repr__(self): - return 'HAL_' + self._fwd_map[self] + return "HAL_" + self._fwd_map[self] def __str__(self): return self.__repr__() @@ -54,7 +55,7 @@ class HalPinDir(HalPinAttrBase): .. inheritance-diagram:: hal_hw_interface.hal_pin_attrs.HalPinDir """ - _suffixes = set(['IN', 'OUT', 'IO']) + _suffixes = set(["IN", "OUT", "IO"]) class HalPinType(HalPinAttrBase): @@ -69,4 +70,4 @@ class HalPinType(HalPinAttrBase): .. inheritance-diagram:: hal_hw_interface.hal_pin_attrs.HalPinType """ - _suffixes = set(['BIT', 'U32', 'S32', 'FLOAT']) + _suffixes = set(["BIT", "U32", "S32", "FLOAT"]) diff --git a/hal_hw_interface/src/hal_hw_interface/loadrt_local.py b/hal_hw_interface/src/hal_hw_interface/loadrt_local.py index 940553a..7cc140f 100644 --- a/hal_hw_interface/src/hal_hw_interface/loadrt_local.py +++ b/hal_hw_interface/src/hal_hw_interface/loadrt_local.py @@ -9,11 +9,11 @@ def loadrt_local(modname): """ if modname in hal.components: return - for path in os.environ.get('LD_LIBRARY_PATH', '').split(':'): + for path in os.environ.get("LD_LIBRARY_PATH", "").split(":"): rospy.logdebug(f"Checking for {modname}.so in {path}") - modpath = os.path.join(path, f'{modname}') - if os.path.exists(f'{modpath}.so'): + modpath = os.path.join(path, f"{modname}") + if os.path.exists(f"{modpath}.so"): break else: - raise RuntimeError(f'Unable to locate {modname} module') + raise RuntimeError(f"Unable to locate {modname} module") rtapi.loadrt(modpath) diff --git a/hal_hw_interface/src/hal_hw_interface/redis_store_hal_pin.py b/hal_hw_interface/src/hal_hw_interface/redis_store_hal_pin.py index 036e873..bc82f69 100644 --- a/hal_hw_interface/src/hal_hw_interface/redis_store_hal_pin.py +++ b/hal_hw_interface/src/hal_hw_interface/redis_store_hal_pin.py @@ -45,30 +45,30 @@ class RedisStoreHalPin(RosHalPin): # Attribute default factories @key.default def _key_default(self): - return '{}/{}'.format(self.compname, self.pin_name) + return "{}/{}".format(self.compname, self.pin_name) redis_service_timeout_default = 20.0 # seconds to wait for service @property def _redis_config(self): - if 'redis_config_client' in self._cached_objs: - return self._cached_objs['redis_config_client'] + if "redis_config_client" in self._cached_objs: + return self._cached_objs["redis_config_client"] # Autovivify timeout = self.get_ros_param( - 'redis_service_timeout', self.redis_service_timeout_default + "redis_service_timeout", self.redis_service_timeout_default ) rospy.loginfo(f"Connecting to redis database, timeout {timeout}s") client = redis_config.ConfigClient(subscribe=True) client.wait_for_service(timeout=timeout) - self._cached_objs['redis_config_client'] = client + self._cached_objs["redis_config_client"] = client # Disconnect from redis at shutdown self.add_shutdown_callback(client.stop) return client def _ros_init(self): - if self.hal_dir == HalPinDir('IN'): + if self.hal_dir == HalPinDir("IN"): # Input pins write value out to redis self._prev_pin_val = self.get_pin() self._prev_redis_val = None @@ -93,7 +93,7 @@ def _update_fm_redis(self, key, value): def update(self): """Write changed pin value to redis for input and IO pins""" new_val = self.get_pin() - if self.hal_dir == HalPinDir('OUT') or self._prev_pin_val == new_val: + if self.hal_dir == HalPinDir("OUT") or self._prev_pin_val == new_val: return # Not applicable self._redis_config.set_param(self.key, new_val) self._prev_pin_val = self._prev_redis_val = new_val diff --git a/hal_hw_interface/src/hal_hw_interface/ros_hal_component.py b/hal_hw_interface/src/hal_hw_interface/ros_hal_component.py index 2b311c8..0c9996b 100644 --- a/hal_hw_interface/src/hal_hw_interface/ros_hal_component.py +++ b/hal_hw_interface/src/hal_hw_interface/ros_hal_component.py @@ -67,7 +67,7 @@ def __init__(self): rospy.loginfo("Initializing '%s' component" % self.compname) # Publisher update rate in Hz - self.update_rate = self.get_ros_param('update_rate', 10) + self.update_rate = self.get_ros_param("update_rate", 10) self.rate = rospy.Rate(self.update_rate) rospy.logdebug("Publish update rate = %.1f" % self.update_rate) @@ -124,7 +124,7 @@ def shutdown_component(self): Executes the list of callbacks defined by calls to :py:func:`hal_hw_interface.hal_obj_base.HalObjBase.add_shutdown_callback`. """ - for cb in self._cached_objs.setdefault('shutdown_cbs', []): + for cb in self._cached_objs.setdefault("shutdown_cbs", []): cb() def main(self): diff --git a/hal_hw_interface/src/hal_hw_interface/ros_hal_pin.py b/hal_hw_interface/src/hal_hw_interface/ros_hal_pin.py index 187ecff..b1a5488 100644 --- a/hal_hw_interface/src/hal_hw_interface/ros_hal_pin.py +++ b/hal_hw_interface/src/hal_hw_interface/ros_hal_pin.py @@ -44,7 +44,7 @@ class RosHalPin(HalObjBase): """ _default_hal_type = None - _default_hal_dir = 'IN' # Subclasses may override for hal_dir attribute + _default_hal_dir = "IN" # Subclasses may override for hal_dir attribute name = attr.ib() hal_type = attr.ib(converter=HalPinType) @@ -58,7 +58,7 @@ def _hal_dir_default(self): @hal_type.default def _hal_type_default(self): if self._default_hal_type is None: - raise TypeError('%s requires hal_type= argument' % self.__class__) + raise TypeError("%s requires hal_type= argument" % self.__class__) return HalPinType(self._default_hal_type) @property @@ -161,20 +161,20 @@ class RosHalPinPublisher(RosHalPin): .. todo:: Link documentation to ROS ``srv`` messages """ - _default_hal_dir = 'IN' + _default_hal_dir = "IN" pub_topic = attr.ib() msg_type = attr.ib() # Attribute default factories @pub_topic.default def _pub_topic_default(self): - return '{}/{}'.format(self.compname, self.pin_name) + return "{}/{}".format(self.compname, self.pin_name) _pin_to_msg_type_map = { - HalPinType('BIT'): Bool, - HalPinType('U32'): UInt32, - HalPinType('S32'): Int32, - HalPinType('FLOAT'): Float64, + HalPinType("BIT"): Bool, + HalPinType("U32"): UInt32, + HalPinType("S32"): Int32, + HalPinType("FLOAT"): Float64, } @msg_type.default @@ -192,12 +192,12 @@ def _ros_publisher_init(self): ) def _value_changed(self, value): - if self.hal_type == HalPinType('FLOAT'): + if self.hal_type == HalPinType("FLOAT"): changed = not self._isclose( self._msg.data, value, - rel_tol=self.get_ros_param('relative_tolerance', 1e-9), - abs_tol=self.get_ros_param('absolute_tolerance', 1e-9), + rel_tol=self.get_ros_param("relative_tolerance", 1e-9), + abs_tol=self.get_ros_param("absolute_tolerance", 1e-9), ) else: changed = self._msg.data != value @@ -212,7 +212,8 @@ def update(self): % (self.pin_name, self.get_pin()) ) rospy.loginfo( - f'Pin {self.pin_name} changed: old={self._msg.data}; new={value}' + f"Pin {self.pin_name} changed:" + " old={self._msg.data}; new={value}" ) self._msg.data = value self.pub.publish(self._msg) @@ -243,13 +244,13 @@ class RosHalPinSubscriber(RosHalPinPublisher): :type sub_topic: str """ - _default_hal_dir = 'OUT' + _default_hal_dir = "OUT" sub_topic = attr.ib() # Attribute default factories @sub_topic.default def _sub_topic_default(self): - return '{}/{}'.format(self.compname, self.pin_name) + return "{}/{}".format(self.compname, self.pin_name) def _ros_init(self): super()._ros_init() @@ -304,7 +305,7 @@ class RosHalPinService(RosHalPinPublisher): :type service_name: str """ - _default_hal_dir = 'OUT' + _default_hal_dir = "OUT" service_name = attr.ib() service_msg_type = attr.ib() @@ -312,13 +313,13 @@ class RosHalPinService(RosHalPinPublisher): # Attribute default factories @service_name.default def _service_name_default(self): - return '{}/{}'.format(self.compname, self.pin_name) + return "{}/{}".format(self.compname, self.pin_name) _pin_to_service_msg_type_map = { - HalPinType('BIT'): SetBool, - HalPinType('U32'): SetUInt32, - HalPinType('S32'): SetInt32, - HalPinType('FLOAT'): SetFloat64, + HalPinType("BIT"): SetBool, + HalPinType("U32"): SetUInt32, + HalPinType("S32"): SetInt32, + HalPinType("FLOAT"): SetFloat64, } @service_msg_type.default @@ -337,4 +338,4 @@ def _ros_service_init(self): def _svc_cb(self, req): self.set_pin(req.data) - return True, 'OK' + return True, "OK" diff --git a/hal_hw_interface/src/hal_hw_interface/tests/conftest.py b/hal_hw_interface/src/hal_hw_interface/tests/conftest.py index bbbaa5e..5beffb9 100644 --- a/hal_hw_interface/src/hal_hw_interface/tests/conftest.py +++ b/hal_hw_interface/src/hal_hw_interface/tests/conftest.py @@ -22,7 +22,7 @@ def get_pin(key): value = pin_values[key] print("Returning pin %s value=%s" % (key, value)) else: - value = pin_values['__default'] + value = pin_values["__default"] print("Returning pin %s DEFAULT value=0x%x" % (key, value)) return value @@ -30,28 +30,28 @@ def set_pin(key, value): print("Setting pin %s value=%s" % (key, value)) pin_values[key] = value - mock_objs_dict['comp_name'] = 'test_comp' - comp_getprefix = MagicMock(side_effect=lambda: mock_objs_dict['comp_name']) + mock_objs_dict["comp_name"] = "test_comp" + comp_getprefix = MagicMock(side_effect=lambda: mock_objs_dict["comp_name"]) def set_comp_name(n): - mock_objs_dict['comp_name'] = n + mock_objs_dict["comp_name"] = n comp_setprefix = MagicMock(side_effect=set_comp_name) - mock_comp_obj = MagicMock(name='mock_hal_comp_obj') + mock_comp_obj = MagicMock(name="mock_hal_comp_obj") mock_comp_obj.configure_mock( - name='mock_hal_comp_obj', + name="mock_hal_comp_obj", getprefix=comp_getprefix, setprefix=comp_setprefix, set_pin=set_pin, **{ - '__getitem__.side_effect': get_pin, - '__setitem__.side_effect': set_pin, + "__getitem__.side_effect": get_pin, + "__setitem__.side_effect": set_pin, } ) - patcher = patch('hal.component', return_value=mock_comp_obj) + patcher = patch("hal.component", return_value=mock_comp_obj) mock_hal = patcher.start() - mock_objs_dict['hal_comp'] = mock_hal # Pass hal.component fixture + mock_objs_dict["hal_comp"] = mock_hal # Pass hal.component fixture yield mock_comp_obj patcher.stop() @@ -63,24 +63,24 @@ def mock_rospy(): def set_key(key, value): get_param_keys[key] = value - mock_get_param = MagicMock(name='mock_rospy_get_param') + mock_get_param = MagicMock(name="mock_rospy_get_param") get_param_keys = dict() mock_get_param.side_effect = get_param_keys.get mock_get_param.set_key = set_key # - rospy.Rate with mock rospy.Rate() - mock_Rate_obj = MagicMock(name='mock_rospy_Rate_obj') + mock_Rate_obj = MagicMock(name="mock_rospy_Rate_obj") mock_Rate = MagicMock(return_value=mock_Rate_obj) # - rospy.is_shutdown() that shuts down after a few loops mock_is_shutdown = MagicMock(side_effect=[False] * 3 + [True]) # - rospy.{Subscriber,Publisher,Service}() methods & returned objects - mock_Subscriber_obj = MagicMock(name='mock_rospy_Subscriber_obj') + mock_Subscriber_obj = MagicMock(name="mock_rospy_Subscriber_obj") mock_Subscriber = MagicMock(return_value=mock_Subscriber_obj) - mock_Publisher_obj = MagicMock(name='mock_rospy_Publisher_obj') + mock_Publisher_obj = MagicMock(name="mock_rospy_Publisher_obj") mock_Publisher = MagicMock(return_value=mock_Publisher_obj) - mock_Service_obj = MagicMock(name='mock_rospy_Service_obj') + mock_Service_obj = MagicMock(name="mock_rospy_Service_obj") mock_Service = MagicMock(return_value=mock_Service_obj) # The patch.multiple() patcher doesn't pass non-DEFAULT @@ -109,13 +109,13 @@ def log_side_effect(msg_fmt, *args): return log_side_effect mock_loginfo = MagicMock( - name='rospy_loginfo', side_effect=log_side_effect_closure('loginfo') + name="rospy_loginfo", side_effect=log_side_effect_closure("loginfo") ) mock_logdebug = MagicMock( - name='rospy_logdebug', side_effect=log_side_effect_closure('logdebug') + name="rospy_logdebug", side_effect=log_side_effect_closure("logdebug") ) mock_logfatal = MagicMock( - name='rospy_logfatal', side_effect=log_side_effect_closure('logfatal') + name="rospy_logfatal", side_effect=log_side_effect_closure("logfatal") ) # patch ropsy @@ -131,7 +131,7 @@ def log_side_effect(msg_fmt, *args): Service=mock_Service, is_shutdown=mock_is_shutdown, ) - patcher = patch.multiple('rospy', **rpc) + patcher = patch.multiple("rospy", **rpc) mock_rospy = patcher.start() yield mock_rospy @@ -146,7 +146,7 @@ def mock_redis_client_obj(request): request.instance.key_value_map = key_value_map = dict(__default=0) def get_key(key): - value = key_value_map.get(key, key_value_map['__default']) + value = key_value_map.get(key, key_value_map["__default"]) print("Returning redis key %s value=%s" % (key, value)) return value @@ -154,19 +154,19 @@ def set_key(key, value): print("Setting redis key %s value=%s" % (key, value)) key_value_map[key] = value - mock_client_obj = MagicMock(name='ConfigClient_obj') + mock_client_obj = MagicMock(name="ConfigClient_obj") mock_client_obj.configure_mock( - name='mock_redis_client_obj', + name="mock_redis_client_obj", set_key=set_key, # Won't increment mock_calls on_update_received=list(), - **{'get_param.side_effect': get_key, 'set_param.side_effect': set_key} + **{"get_param.side_effect": get_key, "set_param.side_effect": set_key} ) patcher = patch( - 'redis_store.config.ConfigClient', return_value=mock_client_obj + "redis_store.config.ConfigClient", return_value=mock_client_obj ) redis_store = patcher.start() - mock_objs_dict['redis_store'] = redis_store + mock_objs_dict["redis_store"] = redis_store yield mock_client_obj patcher.stop() diff --git a/hal_hw_interface/src/hal_hw_interface/tests/test_fixtures.py b/hal_hw_interface/src/hal_hw_interface/tests/test_fixtures.py index 3d853af..21b08ad 100644 --- a/hal_hw_interface/src/hal_hw_interface/tests/test_fixtures.py +++ b/hal_hw_interface/src/hal_hw_interface/tests/test_fixtures.py @@ -10,7 +10,7 @@ class TestFixtures: def test_mock_comp_obj_fixture(self, mock_comp_obj, mock_objs): # Test hal.component returns mock_comp_obj - assert mock_objs['hal_comp']() is mock_comp_obj + assert mock_objs["hal_comp"]() is mock_comp_obj # Set each pin (with out-of-band method) and check for name, value in keys1.items(): @@ -33,27 +33,27 @@ def test_mock_comp_obj_fixture(self, mock_comp_obj, mock_objs): assert mock_comp_obj[name] == value # Default case - assert mock_comp_obj['bogus'] == 0xDEADBEEF + assert mock_comp_obj["bogus"] == 0xDEADBEEF def test_mock_rospy_fixture(self, mock_rospy, mock_objs): # Test mock rospy.get_param() - gp = mock_objs['rospy_get_param'] - gp.set_key('foo', 1) - gp.set_key('bar', 2) - assert gp('foo') == 1 - assert gp('bar') == 2 - gp.set_key('baz', 3) - assert gp('foo') == 1 - assert gp('bar') == 2 - assert gp('baz') == 3 + gp = mock_objs["rospy_get_param"] + gp.set_key("foo", 1) + gp.set_key("bar", 2) + assert gp("foo") == 1 + assert gp("bar") == 2 + gp.set_key("baz", 3) + assert gp("foo") == 1 + assert gp("bar") == 2 + assert gp("baz") == 3 # Test rospy.Rate() returns expected object - assert mock_objs['rospy_Rate']() is mock_objs['rospy_Rate_obj'] + assert mock_objs["rospy_Rate"]() is mock_objs["rospy_Rate_obj"] # Test rospy.is_shutdown() returns True values, then False found_false = False for i in range(10): - val = mock_objs['rospy_is_shutdown']() + val = mock_objs["rospy_is_shutdown"]() print("iter {} val {}".format(i, val)) if val is False: found_false = True @@ -65,14 +65,14 @@ def test_mock_rospy_fixture(self, mock_rospy, mock_objs): raise Exception("is_shutdown never returned False") # Test returned objects - for name in ('Subscriber', 'Publisher', 'Service'): - method = mock_objs['rospy_{}'.format(name)] - obj = mock_objs['rospy_{}_obj'.format(name)] + for name in ("Subscriber", "Publisher", "Service"): + method = mock_objs["rospy_{}".format(name)] + obj = mock_objs["rospy_{}_obj".format(name)] assert method() == obj def test_mock_redis_client_obj(self, mock_redis_client_obj, mock_objs): # Test redis_store.ConfigClient() returns object - assert mock_objs['redis_store']() is mock_redis_client_obj + assert mock_objs["redis_store"]() is mock_redis_client_obj # Set each param (with out-of-band method) and check for name, value in keys1.items(): @@ -95,4 +95,4 @@ def test_mock_redis_client_obj(self, mock_redis_client_obj, mock_objs): assert mock_redis_client_obj.get_param(name) == value # Default case - assert mock_redis_client_obj.get_param('bogus') == 0 + assert mock_redis_client_obj.get_param("bogus") == 0 diff --git a/hal_hw_interface/src/hal_hw_interface/tests/test_hal_io_comp.py b/hal_hw_interface/src/hal_hw_interface/tests/test_hal_io_comp.py index 7677f0d..f69df60 100644 --- a/hal_hw_interface/src/hal_hw_interface/tests/test_hal_io_comp.py +++ b/hal_hw_interface/src/hal_hw_interface/tests/test_hal_io_comp.py @@ -8,30 +8,30 @@ class TestHalIO(object): @pytest.fixture def pin_params(self, mock_rospy, mock_objs): - gp = mock_objs['rospy_get_param'] + gp = mock_objs["rospy_get_param"] test_pins = dict( publish_pins=dict( - bit_pub=dict(hal_type='HAL_BIT'), - u32_pub=dict(hal_type='HAL_U32'), - s32_pub=dict(hal_type='HAL_S32', hal_dir='HAL_IO'), - float_pub=dict(hal_type='HAL_FLOAT', pub_topic='/float/topic'), + bit_pub=dict(hal_type="HAL_BIT"), + u32_pub=dict(hal_type="HAL_U32"), + s32_pub=dict(hal_type="HAL_S32", hal_dir="HAL_IO"), + float_pub=dict(hal_type="HAL_FLOAT", pub_topic="/float/topic"), ), subscribe_pins=dict( - bit_sub=dict(hal_type='HAL_BIT', sub_topic='/bit/topic'), - u32_sub=dict(hal_type='HAL_U32', hal_dir='HAL_IO'), - s32_sub=dict(hal_type='HAL_S32'), - float_sub=dict(hal_type='HAL_FLOAT'), + bit_sub=dict(hal_type="HAL_BIT", sub_topic="/bit/topic"), + u32_sub=dict(hal_type="HAL_U32", hal_dir="HAL_IO"), + s32_sub=dict(hal_type="HAL_S32"), + float_sub=dict(hal_type="HAL_FLOAT"), ), service_pins=dict( - bit_svc=dict(hal_type='HAL_BIT'), - u32_svc=dict(hal_type='HAL_U32', hal_dir='HAL_IO'), - s32_svc=dict(hal_type='HAL_S32', service_name='/s32/svc'), - float_svc=dict(hal_type='HAL_FLOAT'), + bit_svc=dict(hal_type="HAL_BIT"), + u32_svc=dict(hal_type="HAL_U32", hal_dir="HAL_IO"), + s32_svc=dict(hal_type="HAL_S32", service_name="/s32/svc"), + float_svc=dict(hal_type="HAL_FLOAT"), ), ) for key, val in test_pins.items(): - gp.set_key('hal_io/%s' % key, val) + gp.set_key("hal_io/%s" % key, val) return gp, test_pins @pytest.fixture @@ -39,14 +39,14 @@ def obj(self, mock_comp_obj, mock_rospy, mock_objs, pin_params): for key in list(self.test_class._cached_objs.keys()): self.test_class._cached_objs.pop(key) - mock_comp_obj.setprefix('hal_io') + mock_comp_obj.setprefix("hal_io") return self.test_class() def test_hal_io_comp_fixture(self, pin_params): get_param, test_pins = pin_params - print(get_param('hal_io/publish_pins')) - assert isinstance(get_param('hal_io/publish_pins'), dict) - assert 'bit_sub' in get_param('hal_io/subscribe_pins') + print(get_param("hal_io/publish_pins")) + assert isinstance(get_param("hal_io/publish_pins"), dict) + assert "bit_sub" in get_param("hal_io/subscribe_pins") def test_hal_io_comp_setup_component(self, obj, pin_params): get_param, test_pins = pin_params @@ -60,7 +60,7 @@ def test_hal_io_comp_setup_component(self, obj, pin_params): for config_key, pins in test_pins.items(): # Check param server was queried print("Pin class: %s" % config_key) - get_param.assert_any_call('hal_io/%s' % config_key, {}) + get_param.assert_any_call("hal_io/%s" % config_key, {}) for pin_name, pin_data in pins.items(): # Check pin was created assert pin_name in obj_pins diff --git a/hal_hw_interface/src/hal_hw_interface/tests/test_hal_obj_base.py b/hal_hw_interface/src/hal_hw_interface/tests/test_hal_obj_base.py index f88d75a..0dc952a 100644 --- a/hal_hw_interface/src/hal_hw_interface/tests/test_hal_obj_base.py +++ b/hal_hw_interface/src/hal_hw_interface/tests/test_hal_obj_base.py @@ -5,7 +5,7 @@ class TestHalObjBase(object): - compname = 'test_comp' + compname = "test_comp" test_class = HalObjBase @pytest.fixture @@ -16,14 +16,14 @@ def obj(self, mock_rospy): class BogusComp(HalObjBase): compname = self.compname - BogusComp._cached_objs.pop('hal_comp', None) # Clean fixture + BogusComp._cached_objs.pop("hal_comp", None) # Clean fixture return BogusComp() def test_hal_obj_base_init_hal_comp(self, obj, mock_comp_obj): # Test init_hal_comp() creates cached component object obj.init_hal_comp() - assert 'hal_comp' in obj._cached_objs - assert obj._cached_objs['hal_comp'] is mock_comp_obj + assert "hal_comp" in obj._cached_objs + assert obj._cached_objs["hal_comp"] is mock_comp_obj def test_hal_obj_base_init_hal_comp_no_compname(self): # Test init_hal_comp() on class with no 'compname' attribute @@ -52,12 +52,12 @@ def test_hal_obj_base_hal_comp_not_initialized(self, obj, mock_comp_obj): obj.hal_comp def test_hal_obj_base_get_ros_param(self, obj, mock_objs): - test_params = dict(key1=42, key2='val2') - gp = mock_objs['rospy_get_param'] + test_params = dict(key1=42, key2="val2") + gp = mock_objs["rospy_get_param"] for key_short, set_val in test_params.items(): # Mock return value - key_long = '{}/{}'.format(self.compname, key_short) + key_long = "{}/{}".format(self.compname, key_short) gp.set_key(key_long, set_val) # Call get_ros_param() and check @@ -66,8 +66,8 @@ def test_hal_obj_base_get_ros_param(self, obj, mock_objs): assert get_val == set_val # Check default plumbing - get_val = obj.get_ros_param('bogus_key', default='default_val') + get_val = obj.get_ros_param("bogus_key", default="default_val") gp.assert_called_with( - '{}/bogus_key'.format(self.compname), 'default_val' + "{}/bogus_key".format(self.compname), "default_val" ) - assert get_val == 'default_val' + assert get_val == "default_val" diff --git a/hal_hw_interface/src/hal_hw_interface/tests/test_hal_pin_attrs.py b/hal_hw_interface/src/hal_hw_interface/tests/test_hal_pin_attrs.py index a71f6f1..5c7083c 100644 --- a/hal_hw_interface/src/hal_hw_interface/tests/test_hal_pin_attrs.py +++ b/hal_hw_interface/src/hal_hw_interface/tests/test_hal_pin_attrs.py @@ -6,14 +6,14 @@ valid_cases = [ # Pin direction - (HalPinDir, 'IN', hal.HAL_IN), - (HalPinDir, 'OUT', hal.HAL_OUT), - (HalPinDir, 'IO', hal.HAL_IO), + (HalPinDir, "IN", hal.HAL_IN), + (HalPinDir, "OUT", hal.HAL_OUT), + (HalPinDir, "IO", hal.HAL_IO), # Pin type - (HalPinType, 'BIT', hal.HAL_BIT), - (HalPinType, 'U32', hal.HAL_U32), - (HalPinType, 'S32', hal.HAL_S32), - (HalPinType, 'FLOAT', hal.HAL_FLOAT), + (HalPinType, "BIT", hal.HAL_BIT), + (HalPinType, "U32", hal.HAL_U32), + (HalPinType, "S32", hal.HAL_S32), + (HalPinType, "FLOAT", hal.HAL_FLOAT), ] @@ -27,7 +27,7 @@ def test_hal_pin_attrs_valid_case_fixture(valid_case): assert len(valid_case) == 3 obj_type, short_name, int_val = valid_case assert obj_type in (HalPinDir, HalPinType) - assert short_name in ('IN', 'OUT', 'IO', 'BIT', 'U32', 'S32', 'FLOAT') + assert short_name in ("IN", "OUT", "IO", "BIT", "U32", "S32", "FLOAT") def test_hal_pin_attr_new_from_short_name(valid_case): @@ -40,7 +40,7 @@ def test_hal_pin_attr_new_from_short_name(valid_case): def test_hal_pin_attr_new_from_long(valid_case): # Test init from long name, e.g. 'HAL_IN' obj_type, short_name, int_val = valid_case - test_val = obj_type('HAL_' + short_name) + test_val = obj_type("HAL_" + short_name) assert test_val == int_val @@ -55,20 +55,20 @@ def test_hal_pin_attr_repr(valid_case): # Test __repr__ returns e.g. 'HAL_IN' obj_type, short_name, int_val = valid_case test_val = obj_type(int_val) - assert repr(test_val) == 'HAL_' + short_name + assert repr(test_val) == "HAL_" + short_name def test_hal_pin_attr_str(valid_case): # Test __str__ returns e.g. 'HAL_IN' obj_type, short_name, int_val = valid_case test_val = obj_type(int_val) - assert str(test_val) == 'HAL_' + short_name + assert str(test_val) == "HAL_" + short_name invalid_cases = [ # Pin direction - (HalPinDir, 'INY'), - (HalPinDir, 'in'), + (HalPinDir, "INY"), + (HalPinDir, "in"), (HalPinDir, hal.HAL_BIT), (HalPinDir, hal.HAL_U32), (HalPinDir, hal.HAL_S32), @@ -77,8 +77,8 @@ def test_hal_pin_attr_str(valid_case): (HalPinDir, None), (HalPinDir, float(hal.HAL_IN)), # Pin type - (HalPinType, 'BITY'), - (HalPinType, 'bit'), + (HalPinType, "BITY"), + (HalPinType, "bit"), (HalPinType, hal.HAL_IN), (HalPinType, hal.HAL_OUT), (HalPinType, hal.HAL_IO), diff --git a/hal_hw_interface/src/hal_hw_interface/tests/test_redis_store_hal_pin.py b/hal_hw_interface/src/hal_hw_interface/tests/test_redis_store_hal_pin.py index fea24f4..9678ff9 100644 --- a/hal_hw_interface/src/hal_hw_interface/tests/test_redis_store_hal_pin.py +++ b/hal_hw_interface/src/hal_hw_interface/tests/test_redis_store_hal_pin.py @@ -7,7 +7,7 @@ class TestRedisStoreHalPin(TestRosHalPin): - default_hal_dir = HalPinDir('OUT') + default_hal_dir = HalPinDir("OUT") test_class = RedisStoreHalPin @pytest.fixture(params=TestRosHalPin.obj_cases) @@ -16,9 +16,9 @@ def obj(self, request, mock_comp_obj, mock_rospy, mock_redis_client_obj): mock_comp_obj.setprefix(self.compname) attrs = dict() params = request.param.copy() - params.setdefault('hal_dir', self.default_hal_dir) - name = params.pop('name') - attr_names = ['hal_comp', 'hal_type', 'hal_dir', 'msg_type'] + params.setdefault("hal_dir", self.default_hal_dir) + name = params.pop("name") + attr_names = ["hal_comp", "hal_type", "hal_dir", "msg_type"] attr_names += self.extra_attrs for attr_name in attr_names: if attr_name in params: @@ -47,9 +47,9 @@ def test_redis_store_pin_redis_config( ): # Check that redis client is created and cached self.setup_hal_obj_base(mock_comp_obj) - c1 = self.test_class('test_redis_pin', 'FLOAT')._redis_config + c1 = self.test_class("test_redis_pin", "FLOAT")._redis_config assert c1 is mock_redis_client_obj - c2 = self.test_class('test_redis_pin2', 'FLOAT')._redis_config + c2 = self.test_class("test_redis_pin2", "FLOAT")._redis_config assert c1 is c2 def test_redis_store_pin_update_fm_redis(self, obj): @@ -59,7 +59,7 @@ def test_redis_store_pin_update_fm_redis(self, obj): assert obj._prev_pin_val == 42 assert obj._prev_redis_val == 42 # When key doesn't match, no change - obj._update_fm_redis('foo', 13) + obj._update_fm_redis("foo", 13) assert self.pin_values[obj.pin_name] == 42 def test_redis_store_pin_ros_init( @@ -68,13 +68,13 @@ def test_redis_store_pin_ros_init( # obj._redis_config.on_update_received = list() # Clear out setup entry # Fake a redis param value & run function under test test_val = self.set_mock_redis_param(obj, data) - self.set_pin(obj, data['pin_value']) + self.set_pin(obj, data["pin_value"]) obj._ros_init() - if str(obj.hal_dir) != 'HAL_IN': + if str(obj.hal_dir) != "HAL_IN": # Check that redis was read print( - 'get_param calls:', mock_redis_client_obj.get_param.mock_calls + "get_param calls:", mock_redis_client_obj.get_param.mock_calls ) mock_redis_client_obj.get_param.assert_called_once_with(obj.key) # Check the returned value @@ -102,7 +102,7 @@ def test_redis_store_pin_set_redis_from_pin( print("--------- Calling method") obj.update() - if str(obj.hal_dir) == 'HAL_OUT': + if str(obj.hal_dir) == "HAL_OUT": obj._redis_config.set_param.assert_not_called() return diff --git a/hal_hw_interface/src/hal_hw_interface/tests/test_ros_hal_component.py b/hal_hw_interface/src/hal_hw_interface/tests/test_ros_hal_component.py index ab90c9b..d0f40f8 100644 --- a/hal_hw_interface/src/hal_hw_interface/tests/test_ros_hal_component.py +++ b/hal_hw_interface/src/hal_hw_interface/tests/test_ros_hal_component.py @@ -13,7 +13,7 @@ def obj(self, mock_comp_obj, mock_rospy, mock_objs): # Simplest test case class StubComp(self.test_class): - compname = 'stub' + compname = "stub" def setup_component(self): self.initialized = True @@ -22,42 +22,42 @@ def setup_component(self): def update(self): self.count += 1 - gp = mock_objs['rospy_get_param'] - gp.set_key('stub/update_rate', 20) - gp.set_key('stub/relative_tolerance', 1e-9) - gp.set_key('stub/absolute_tolerance', 1e-9) + gp = mock_objs["rospy_get_param"] + gp.set_key("stub/update_rate", 20) + gp.set_key("stub/relative_tolerance", 1e-9) + gp.set_key("stub/absolute_tolerance", 1e-9) return StubComp() def test_ros_hal_component_attrs(self, obj): - assert obj.compname == 'stub' + assert obj.compname == "stub" def test_ros_hal_component_init( self, obj, mock_rospy, mock_comp_obj, mock_objs ): """Test RosHalComponent.__init__()""" # ROS node initialized - mock_rospy['init_node'].assert_called_with(obj.compname) + mock_rospy["init_node"].assert_called_with(obj.compname) # obj.rate was created from rospy.Rate with param value - mock_objs['rospy_get_param'].assert_called_once_with( - 'stub/update_rate', 10 + mock_objs["rospy_get_param"].assert_called_once_with( + "stub/update_rate", 10 ) assert obj.update_rate == 20 - mock_objs['rospy_Rate'].assert_called_once_with(20) - assert obj.rate is mock_objs['rospy_Rate_obj'] + mock_objs["rospy_Rate"].assert_called_once_with(20) + assert obj.rate is mock_objs["rospy_Rate_obj"] # Assert obj.hal_comp is a HAL component - mock_objs['hal_comp'].assert_called_once_with('stub') + mock_objs["hal_comp"].assert_called_once_with("stub") assert obj.hal_comp == mock_comp_obj # Assert StubComp.setup_component() called - assert getattr(obj, 'initialized', False) is True + assert getattr(obj, "initialized", False) is True # Assert HAL component initialized obj.hal_comp.ready.assert_called_once_with() def test_ros_hal_component_get_param(self, obj, mock_objs): """Test get_param()""" - res = obj.get_ros_param('relative_tolerance', 42) + res = obj.get_ros_param("relative_tolerance", 42) assert res == 1e-9 - res = obj.get_ros_param('bogus_key', 88) + res = obj.get_ros_param("bogus_key", 88) assert res == 88 def test_ros_hal_component_run(self, obj, mock_objs): @@ -66,4 +66,4 @@ def test_ros_hal_component_run(self, obj, mock_objs): """ obj.run() assert obj.count == 3 - assert mock_objs['rospy_Rate_obj'].sleep.call_count == 3 + assert mock_objs["rospy_Rate_obj"].sleep.call_count == 3 diff --git a/hal_hw_interface/src/hal_hw_interface/tests/test_ros_hal_pin.py b/hal_hw_interface/src/hal_hw_interface/tests/test_ros_hal_pin.py index be5170f..1012d0d 100644 --- a/hal_hw_interface/src/hal_hw_interface/tests/test_ros_hal_pin.py +++ b/hal_hw_interface/src/hal_hw_interface/tests/test_ros_hal_pin.py @@ -25,8 +25,8 @@ class TestRosHalPin(object): test_class = RosHalPin default_hal_type = None - default_hal_dir = HalPinDir('IN') - compname = 'mock_hal_comp_obj' # conftest.py + default_hal_dir = HalPinDir("IN") + compname = "mock_hal_comp_obj" # conftest.py extra_attrs = [] # @@ -37,21 +37,21 @@ class TestRosHalPin(object): # contains enough attributes for superset of all classes obj_cases = [ dict( # 0 Test BIT, IO, names - name='reset', - hal_type='BIT', - hal_dir='IO', - sub_topic='/robot/reset', - pub_topic='/robot/reset', - service_name='/robot/reset', + name="reset", + hal_type="BIT", + hal_dir="IO", + sub_topic="/robot/reset", + pub_topic="/robot/reset", + service_name="/robot/reset", ), - dict(name='bool', hal_type='BIT', hal_dir='OUT'), # 1 Test BIT OUT - dict(name='bool', hal_type='BIT'), # 2 Test BIT defaults - dict(name='u32_out', hal_type='U32', hal_dir='OUT'), # 3 Test U32 OUT - dict(name='u32_out', hal_type='U32', hal_dir='IO'), # 4 Test U32 IN - dict(name='u32_out', hal_type='U32'), # 5 Test U32 default - dict(name='s32_out', hal_type='S32'), # 6 Test S32 default + dict(name="bool", hal_type="BIT", hal_dir="OUT"), # 1 Test BIT OUT + dict(name="bool", hal_type="BIT"), # 2 Test BIT defaults + dict(name="u32_out", hal_type="U32", hal_dir="OUT"), # 3 Test U32 OUT + dict(name="u32_out", hal_type="U32", hal_dir="IO"), # 4 Test U32 IN + dict(name="u32_out", hal_type="U32"), # 5 Test U32 default + dict(name="s32_out", hal_type="S32"), # 6 Test S32 default dict( - name='float_io', hal_type='FLOAT', hal_dir='IO' + name="float_io", hal_type="FLOAT", hal_dir="IO" ), # 7 Test FLOAT IO ] @@ -61,9 +61,9 @@ def obj(self, request, mock_comp_obj, mock_rospy, mock_redis_client_obj): mock_comp_obj.setprefix(self.compname) attrs = dict() params = request.param.copy() - params.setdefault('hal_dir', self.default_hal_dir) - name = params.pop('name') - attr_names = ['hal_comp', 'hal_type', 'hal_dir', 'msg_type'] + params.setdefault("hal_dir", self.default_hal_dir) + name = params.pop("name") + attr_names = ["hal_comp", "hal_type", "hal_dir", "msg_type"] attr_names += self.extra_attrs for attr_name in attr_names: if attr_name in params: @@ -96,7 +96,7 @@ def obj(self, request, mock_comp_obj, mock_rospy, mock_redis_client_obj): @pytest.fixture(params=data_cases) def data(self, request): - request.param.setdefault('changed', True) + request.param.setdefault("changed", True) return request.param isclose_cases = [ @@ -134,13 +134,13 @@ def get_obj_test_param(self, obj, param, default=None): return obj._p.get(param, default) def obj_test_name(self, obj): - return self.get_obj_test_param(obj, 'name') + return self.get_obj_test_param(obj, "name") def hal_dir(self, obj): - return self.get_obj_test_param(obj, 'hal_dir', self.default_hal_dir) + return self.get_obj_test_param(obj, "hal_dir", self.default_hal_dir) def hal_type(self, obj): - return self.get_obj_test_param(obj, 'hal_type') + return self.get_obj_test_param(obj, "hal_type") def msg_type(self, obj): return dict(BIT=Bool, FLOAT=Float64, U32=UInt32, S32=Int32)[ @@ -148,22 +148,22 @@ def msg_type(self, obj): ] data_indexes = { - HalPinType('BIT'): 0, - HalPinType('U32'): 1, - HalPinType('S32'): 2, - HalPinType('FLOAT'): 3, + HalPinType("BIT"): 0, + HalPinType("U32"): 1, + HalPinType("S32"): 2, + HalPinType("FLOAT"): 3, } def set_last_value(self, obj, param): index = self.data_indexes.get(obj.hal_type) - value = param['other_value'][index] + value = param["other_value"][index] obj.last_value = value return value def set_pin(self, obj, param): if isinstance(param, dict): index = self.data_indexes.get(obj.hal_type) - value = param['pin_value'][index] + value = param["pin_value"][index] else: value = param obj.hal_comp.set_pin(self.obj_test_name(obj), value) @@ -171,17 +171,17 @@ def set_pin(self, obj, param): def other_value(self, obj, param): index = self.data_indexes.get(obj.hal_type) - return param['other_value'][index] + return param["other_value"][index] def ros_name(self, obj): - return '{}/{}'.format(self.compname, self.obj_test_name(obj)) + return "{}/{}".format(self.compname, self.obj_test_name(obj)) # # Base class tests # def test_ros_hal_pin_compname(self, mock_comp_obj, all_patches): self.setup_hal_obj_base(mock_comp_obj) - obj = self.test_class('test_pin', 'BIT') + obj = self.test_class("test_pin", "BIT") mock_comp_obj.reset_mock() obj.compname print(mock_comp_obj.mock_calls) @@ -190,24 +190,24 @@ def test_ros_hal_pin_compname(self, mock_comp_obj, all_patches): def test_ros_hal_pin_obj_fixture( self, obj, mock_comp_obj, mock_rospy, mock_objs ): - assert hasattr(obj, '_p') + assert hasattr(obj, "_p") params = obj._p - assert obj.name == params['name'] + assert obj.name == params["name"] assert obj.hal_type == HalPinType( - params.get('hal_type', self.default_hal_type) + params.get("hal_type", self.default_hal_type) ) assert obj.hal_dir == HalPinDir( - params.get('hal_dir', self.default_hal_dir) + params.get("hal_dir", self.default_hal_dir) ) - if 'sub_topic' in params and hasattr(obj, 'sub_topic'): - assert obj.sub_topic == params.get('sub_topic') - if 'pub_topic' in params and hasattr(obj, 'pub_topic'): - assert obj.pub_topic == params.get('pub_topic') - if 'service_name' in params and hasattr(obj, 'service_name'): - assert obj.pub_topic == params.get('service_name') + if "sub_topic" in params and hasattr(obj, "sub_topic"): + assert obj.sub_topic == params.get("sub_topic") + if "pub_topic" in params and hasattr(obj, "pub_topic"): + assert obj.pub_topic == params.get("pub_topic") + if "service_name" in params and hasattr(obj, "service_name"): + assert obj.pub_topic == params.get("service_name") def test_ros_hal_pin_data_fixture(self, data): - for key in ('other_value', 'pin_value'): + for key in ("other_value", "pin_value"): assert key in data assert len(data[key]) == 4 assert isinstance(data[key][0], bool) @@ -255,22 +255,22 @@ def test_ros_hal_pin_default_attr_hal_type(self, all_patches): # needed if self.default_hal_type is None: with pytest.raises(TypeError): - self.test_class('default_hal_type') + self.test_class("default_hal_type") else: - obj = self.test_class('default_hal_type') + obj = self.test_class("default_hal_type") assert obj.hal_type == self.default_hal_type class TestRosHalPinPublisher(TestRosHalPin): - default_hal_dir = HalPinDir('IN') + default_hal_dir = HalPinDir("IN") test_class = RosHalPinPublisher - extra_attrs = ['pub_topic'] + extra_attrs = ["pub_topic"] # # Helpers # def pub_topic(self, obj): - return self.get_obj_test_param(obj, 'pub_topic', self.ros_name(obj)) + return self.get_obj_test_param(obj, "pub_topic", self.ros_name(obj)) def set_last_value(self, obj, param): # Set msg.data @@ -289,46 +289,46 @@ def test_ros_hal_pin_attrs(self, obj, mock_comp_obj): def test_ros_hal_pin_publisher_init(self, obj, mock_objs): """Test that __init__() creates rospy.Publisher object""" - print(mock_objs['rospy_Publisher'].mock_calls) + print(mock_objs["rospy_Publisher"].mock_calls) assert obj.pub_topic == self.pub_topic(obj) - mock_objs['rospy_Publisher'].assert_called_with( + mock_objs["rospy_Publisher"].assert_called_with( self.pub_topic(obj), self.msg_type(obj), queue_size=1, latch=True ) - assert obj.pub is mock_objs['rospy_Publisher_obj'] + assert obj.pub is mock_objs["rospy_Publisher_obj"] def test_ros_hal_pin_publisher_value_changed(self, obj, data, mock_objs): """Test _value_changed() function""" - assert obj.get_ros_param('relative_tolerance', 1e-9) == 1e-9 - assert obj.get_ros_param('absolute_tolerance', 1e-9) == 1e-9 + assert obj.get_ros_param("relative_tolerance", 1e-9) == 1e-9 + assert obj.get_ros_param("absolute_tolerance", 1e-9) == 1e-9 last_value = self.set_last_value(obj, data) cur_value = self.set_pin(obj, data) same = last_value == cur_value - assert same is not data.get('changed') - assert obj._value_changed(cur_value) is data.get('changed') + assert same is not data.get("changed") + assert obj._value_changed(cur_value) is data.get("changed") def test_ros_hal_pin_publisher_update(self, obj, data): self.set_last_value(obj, data) cur_value = self.set_pin(obj, data) obj.update() assert obj._msg.data == cur_value - print(f'pub.publish calls: {obj.pub.publish.mock_calls}') - if data.get('changed'): + print(f"pub.publish calls: {obj.pub.publish.mock_calls}") + if data.get("changed"): obj.pub.publish.assert_called_with(obj._msg) else: obj.pub.publish.assert_not_called class TestRosHalPinSubscriber(TestRosHalPinPublisher): - default_hal_dir = HalPinDir('OUT') + default_hal_dir = HalPinDir("OUT") test_class = RosHalPinSubscriber - extra_attrs = ['pub_topic', 'sub_topic'] + extra_attrs = ["pub_topic", "sub_topic"] # # Helpers # def sub_topic(self, obj): - return self.get_obj_test_param(obj, 'sub_topic', self.ros_name(obj)) + return self.get_obj_test_param(obj, "sub_topic", self.ros_name(obj)) # # Tests @@ -341,10 +341,10 @@ def test_ros_hal_pin_attrs(self, obj, mock_comp_obj): def test_ros_hal_pin_subscriber_init(self, obj, mock_objs): """Test that __init__() creates rospy.Subscriber object""" - mock_objs['rospy_Subscriber'].assert_called_with( + mock_objs["rospy_Subscriber"].assert_called_with( self.sub_topic(obj), self.msg_type(obj), obj._subscriber_cb ) - assert obj.sub is mock_objs['rospy_Subscriber_obj'] + assert obj.sub is mock_objs["rospy_Subscriber_obj"] def test_ros_hal_pin_subscriber_cb(self, obj, data, mock_comp_obj): """Test that HAL pin is set in subscriber callback""" @@ -362,15 +362,15 @@ def test_ros_hal_pin_subscriber_bad_msg_type(self, obj): class TestRosHalPinService(TestRosHalPinPublisher): - default_hal_dir = HalPinDir('OUT') + default_hal_dir = HalPinDir("OUT") test_class = RosHalPinService - extra_attrs = ['pub_topic', 'service_name'] + extra_attrs = ["pub_topic", "service_name"] # # Helpers # def service_name(self, obj): - return self.get_obj_test_param(obj, 'service_name', self.ros_name(obj)) + return self.get_obj_test_param(obj, "service_name", self.ros_name(obj)) def service_msg_type(self, obj): return dict(BIT=SetBool, FLOAT=SetFloat64, U32=SetUInt32, S32=SetInt32)[ @@ -390,10 +390,10 @@ def test_ros_hal_pin_attrs(self, obj, mock_comp_obj): def test_ros_hal_pin_service_init(self, obj, mock_objs): """Test that __init__() creates rospy.Service object""" assert obj.service_name == self.service_name(obj) - mock_objs['rospy_Service'].assert_called_with( + mock_objs["rospy_Service"].assert_called_with( self.service_name(obj), self.service_msg_type(obj), obj._svc_cb ) - assert obj.service is mock_objs['rospy_Service_obj'] + assert obj.service is mock_objs["rospy_Service_obj"] def test_ros_hal_pin_service_cb(self, obj, data): """Test that the HAL pin is set during callback""" @@ -404,4 +404,4 @@ def test_ros_hal_pin_service_cb(self, obj, data): obj.hal_comp.__setitem__.assert_called_with( self.obj_test_name(obj), call_value ) - assert reply == (True, 'OK') + assert reply == (True, "OK") From ec1a86938334b3afc845c67880409b5526dac32a Mon Sep 17 00:00:00 2001 From: John Morris Date: Tue, 26 Oct 2021 13:12:11 -0500 Subject: [PATCH 22/32] hal_mgr: Log missing HAL files as errors Easier to pick out failure reason from logs --- hal_hw_interface/src/hal_hw_interface/hal_mgr.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/hal_hw_interface/src/hal_hw_interface/hal_mgr.py b/hal_hw_interface/src/hal_hw_interface/hal_mgr.py index a695abc..b1c4dbb 100644 --- a/hal_hw_interface/src/hal_hw_interface/hal_mgr.py +++ b/hal_hw_interface/src/hal_hw_interface/hal_mgr.py @@ -77,7 +77,8 @@ def shutdown_graceful(signum, frame): if not os.path.exists(fpath): self.shutdown( "No file '%s' in directory '%s'" - % (fname, hal_mgr_config["hal_file_dir"]) + % (fname, hal_mgr_config["hal_file_dir"]), + res=1, ) rospy.loginfo("hal_mgr: Loading hal file '%s'" % fname) launcher.load_hal_file(fpath) From 358324991420359bce349908112bcceb4d89726f Mon Sep 17 00:00:00 2001 From: John Morris Date: Tue, 26 Oct 2021 13:46:44 -0500 Subject: [PATCH 23/32] hal_rrbot_control: Remove `xacro --inorder` arg Deprecated --- hal_rrbot_control/launch/hal_rrbot_simulation.launch | 2 +- hal_rrbot_control/launch/rrbot_visualize.launch | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/hal_rrbot_control/launch/hal_rrbot_simulation.launch b/hal_rrbot_control/launch/hal_rrbot_simulation.launch index d457c90..7bf11e4 100644 --- a/hal_rrbot_control/launch/hal_rrbot_simulation.launch +++ b/hal_rrbot_control/launch/hal_rrbot_simulation.launch @@ -16,7 +16,7 @@ diff --git a/hal_rrbot_control/launch/rrbot_visualize.launch b/hal_rrbot_control/launch/rrbot_visualize.launch index c2c5cd3..1125d92 100644 --- a/hal_rrbot_control/launch/rrbot_visualize.launch +++ b/hal_rrbot_control/launch/rrbot_visualize.launch @@ -6,7 +6,7 @@ From 2e7b56d1445432c4556525946c7f6e684c28a21b Mon Sep 17 00:00:00 2001 From: John Morris Date: Tue, 26 Oct 2021 15:01:16 -0500 Subject: [PATCH 24/32] Fix shfmt formatter installation This error cropped up a month or so ago: build mvdan.cc/sh/v3/cmd/shfmt: cannot load io/fs: malformed module path "io/fs": missing dot in first path element Fix is to update the golang version. The Debian Buster `golang` package is still at 1.13, whereas the latest release is 1.17. --- .github/workflows/format.yaml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/format.yaml b/.github/workflows/format.yaml index 22130a3..7c117c2 100644 --- a/.github/workflows/format.yaml +++ b/.github/workflows/format.yaml @@ -32,7 +32,9 @@ jobs: run: sudo -H pip3 install black - name: Install shfmt run: | - sudo apt-get install -y golang - sudo GOPATH=/usr/local/go GO111MODULE=on go get mvdan.cc/sh/v3/cmd/shfmt + wget -O /tmp/go.tgz https://golang.org/dl/go1.17.1.linux-amd64.tar.gz + sudo tar -C /usr/local -xzf /tmp/go.tgz + sudo ln -s ../go/bin/go /usr/local/bin/go + sudo GOPATH=/usr/local/go go install mvdan.cc/sh/v3/cmd/shfmt@latest sudo ln -s ../go/bin/shfmt /usr/local/bin/shfmt - uses: pre-commit/action@v2.0.0 From 227db47ffc1e824f35c0988ba27728f965384f5f Mon Sep 17 00:00:00 2001 From: John Morris Date: Fri, 5 Nov 2021 11:58:41 -0500 Subject: [PATCH 25/32] Fix log string formatting --- hal_hw_interface/src/hal_hw_interface/ros_hal_pin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hal_hw_interface/src/hal_hw_interface/ros_hal_pin.py b/hal_hw_interface/src/hal_hw_interface/ros_hal_pin.py index b1a5488..861f9db 100644 --- a/hal_hw_interface/src/hal_hw_interface/ros_hal_pin.py +++ b/hal_hw_interface/src/hal_hw_interface/ros_hal_pin.py @@ -213,7 +213,7 @@ def update(self): ) rospy.loginfo( f"Pin {self.pin_name} changed:" - " old={self._msg.data}; new={value}" + f" old={self._msg.data}; new={value}" ) self._msg.data = value self.pub.publish(self._msg) From e7a9fcc062eafaa821ebccba2ab3bb6639f983cb Mon Sep 17 00:00:00 2001 From: the-snowwhite Date: Sun, 8 Jan 2023 23:16:58 +0000 Subject: [PATCH 26/32] Change include paths to new mk-hal cmake structure Signed-off-by: the-snowwhite --- hal_hw_interface/cmake/FindHAL.cmake | 4 ++-- hal_hw_interface/include/hal_hw_interface/hal_hw_interface.h | 2 +- hal_hw_interface/include/hal_hw_interface/hal_ros_logging.h | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/hal_hw_interface/cmake/FindHAL.cmake b/hal_hw_interface/cmake/FindHAL.cmake index fb7fce3..d360ac2 100644 --- a/hal_hw_interface/cmake/FindHAL.cmake +++ b/hal_hw_interface/cmake/FindHAL.cmake @@ -24,8 +24,8 @@ set(MACHINEKIT_RIP_PATH # HAL_INCLUDE_PATH: Find HAL include directory find_path( - HAL_INCLUDE_PATH hal.h - PATH_SUFFIXES machinekit + HAL_INCLUDE_PATH hal/hal.h + PATH_SUFFIXES machinekit/hal PATHS ${MACHINEKIT_RIP_PATH}/include ) if(HAL_INCLUDE_PATH) diff --git a/hal_hw_interface/include/hal_hw_interface/hal_hw_interface.h b/hal_hw_interface/include/hal_hw_interface/hal_hw_interface.h index 838e229..e9b8b46 100644 --- a/hal_hw_interface/include/hal_hw_interface/hal_hw_interface.h +++ b/hal_hw_interface/include/hal_hw_interface/hal_hw_interface.h @@ -36,7 +36,7 @@ #include // HAL -#include +#include // ROS #include diff --git a/hal_hw_interface/include/hal_hw_interface/hal_ros_logging.h b/hal_hw_interface/include/hal_hw_interface/hal_ros_logging.h index 94dd6e6..47b7586 100644 --- a/hal_hw_interface/include/hal_hw_interface/hal_ros_logging.h +++ b/hal_hw_interface/include/hal_hw_interface/hal_ros_logging.h @@ -32,7 +32,7 @@ #ifndef HAL_HW_INTERFACE_HAL_ROS_LOGGING_H #define HAL_HW_INTERFACE_HAL_ROS_LOGGING_H -#include +#include #include #define HAL_ROS_LOG(hal_lev, ros_lev, name, ...) \ From b3faacedcf5c1602c2130b7775d2f06819bcfdf2 Mon Sep 17 00:00:00 2001 From: the-snowwhite Date: Mon, 9 Jan 2023 00:45:06 +0100 Subject: [PATCH 27/32] change python module import paths to new cmake paths Signed-off-by: the-snowwhite --- hal_hw_interface/src/hal_hw_interface/hal_mgr.py | 7 ++++--- hal_hw_interface/src/hal_hw_interface/loadrt_local.py | 3 ++- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/hal_hw_interface/src/hal_hw_interface/hal_mgr.py b/hal_hw_interface/src/hal_hw_interface/hal_mgr.py index b1c4dbb..893a855 100644 --- a/hal_hw_interface/src/hal_hw_interface/hal_mgr.py +++ b/hal_hw_interface/src/hal_hw_interface/hal_mgr.py @@ -4,7 +4,8 @@ import subprocess import signal -from machinekit import launcher, config, rtapi +from machinekit.hal import launcher +from machinekit.hal.cyruntime import rtapi # ROS import rospy @@ -114,8 +115,8 @@ def main(): launcher.set_debug_level(debug) if "MACHINEKIT_INI" not in os.environ: # export for package installs - mkconfig = config.Config() - os.environ["MACHINEKIT_INI"] = mkconfig.MACHINEKIT_INI + #mkconfig = config.Config() + os.environ["MACHINEKIT_INI"] = os.getenv("MACHINEKIT_INI") #mkconfig.MACHINEKIT_INI hal_mgr = HalMgr() try: diff --git a/hal_hw_interface/src/hal_hw_interface/loadrt_local.py b/hal_hw_interface/src/hal_hw_interface/loadrt_local.py index 7cc140f..2f2fafe 100644 --- a/hal_hw_interface/src/hal_hw_interface/loadrt_local.py +++ b/hal_hw_interface/src/hal_hw_interface/loadrt_local.py @@ -1,6 +1,7 @@ import rospy import os -from machinekit import rtapi, hal +from machinekit import hal +from machinekit.hal.cyruntime import rtapi def loadrt_local(modname): From aeffeaac17df6be1d895e7f914bf47d7cff4a93d Mon Sep 17 00:00:00 2001 From: the-snowwhite Date: Mon, 9 Jan 2023 00:45:06 +0100 Subject: [PATCH 28/32] change python module import paths to new cmake paths Signed-off-by: the-snowwhite --- hal_hw_interface/src/hal_hw_interface/hal_mgr.py | 7 ++++--- hal_hw_interface/src/hal_hw_interface/loadrt_local.py | 3 ++- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/hal_hw_interface/src/hal_hw_interface/hal_mgr.py b/hal_hw_interface/src/hal_hw_interface/hal_mgr.py index b1c4dbb..4ded95c 100644 --- a/hal_hw_interface/src/hal_hw_interface/hal_mgr.py +++ b/hal_hw_interface/src/hal_hw_interface/hal_mgr.py @@ -4,7 +4,8 @@ import subprocess import signal -from machinekit import launcher, config, rtapi +from machinekit.hal.launcher import launcher +from machinekit.hal.cyruntime import rtapi # ROS import rospy @@ -114,8 +115,8 @@ def main(): launcher.set_debug_level(debug) if "MACHINEKIT_INI" not in os.environ: # export for package installs - mkconfig = config.Config() - os.environ["MACHINEKIT_INI"] = mkconfig.MACHINEKIT_INI + #mkconfig = config.Config() + os.environ["MACHINEKIT_INI"] = os.getenv("MACHINEKIT_INI") #mkconfig.MACHINEKIT_INI hal_mgr = HalMgr() try: diff --git a/hal_hw_interface/src/hal_hw_interface/loadrt_local.py b/hal_hw_interface/src/hal_hw_interface/loadrt_local.py index 7cc140f..2f2fafe 100644 --- a/hal_hw_interface/src/hal_hw_interface/loadrt_local.py +++ b/hal_hw_interface/src/hal_hw_interface/loadrt_local.py @@ -1,6 +1,7 @@ import rospy import os -from machinekit import rtapi, hal +from machinekit import hal +from machinekit.hal.cyruntime import rtapi def loadrt_local(modname): From 9eff4d766a437f555f0d45d9a7d6345ee4fe9b76 Mon Sep 17 00:00:00 2001 From: the-snowwhite Date: Mon, 9 Jan 2023 10:27:25 +0100 Subject: [PATCH 29/32] remedy for missing config module Signed-off-by: the-snowwhite --- hal_hw_interface/src/hal_hw_interface/hal_mgr.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hal_hw_interface/src/hal_hw_interface/hal_mgr.py b/hal_hw_interface/src/hal_hw_interface/hal_mgr.py index 4ded95c..b08a9a9 100644 --- a/hal_hw_interface/src/hal_hw_interface/hal_mgr.py +++ b/hal_hw_interface/src/hal_hw_interface/hal_mgr.py @@ -116,7 +116,7 @@ def main(): if "MACHINEKIT_INI" not in os.environ: # export for package installs #mkconfig = config.Config() - os.environ["MACHINEKIT_INI"] = os.getenv("MACHINEKIT_INI") #mkconfig.MACHINEKIT_INI + os.environ["MACHINEKIT_INI"] = machinekit_hal_ini_file #mkconfig.MACHINEKIT_INI hal_mgr = HalMgr() try: From 54b86732820e7ba2311998566b41c69f917c61e0 Mon Sep 17 00:00:00 2001 From: the-snowwhite Date: Tue, 10 Jan 2023 04:01:53 +0100 Subject: [PATCH 30/32] Fix import hal paths Signed-off-by: the-snowwhite --- hal_hw_interface/src/hal_hw_interface/hal_mgr.py | 2 +- hal_hw_interface/src/hal_hw_interface/hal_obj_base.py | 2 +- hal_hw_interface/src/hal_hw_interface/hal_pin_attrs.py | 2 +- .../src/hal_hw_interface/tests/test_hal_pin_attrs.py | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/hal_hw_interface/src/hal_hw_interface/hal_mgr.py b/hal_hw_interface/src/hal_hw_interface/hal_mgr.py index b08a9a9..91f62f7 100644 --- a/hal_hw_interface/src/hal_hw_interface/hal_mgr.py +++ b/hal_hw_interface/src/hal_hw_interface/hal_mgr.py @@ -116,7 +116,7 @@ def main(): if "MACHINEKIT_INI" not in os.environ: # export for package installs #mkconfig = config.Config() - os.environ["MACHINEKIT_INI"] = machinekit_hal_ini_file #mkconfig.MACHINEKIT_INI + os.environ["MACHINEKIT_INI"] = "/etc/machinekit/hal/machinekit.ini" #mkconfig.MACHINEKIT_INI hal_mgr = HalMgr() try: diff --git a/hal_hw_interface/src/hal_hw_interface/hal_obj_base.py b/hal_hw_interface/src/hal_hw_interface/hal_obj_base.py index 04cdd47..a12d6c3 100644 --- a/hal_hw_interface/src/hal_hw_interface/hal_obj_base.py +++ b/hal_hw_interface/src/hal_hw_interface/hal_obj_base.py @@ -7,7 +7,7 @@ .. moduleauthor:: John Morris """ -import hal +import machinekit.hal.cyhal as hal import rospy diff --git a/hal_hw_interface/src/hal_hw_interface/hal_pin_attrs.py b/hal_hw_interface/src/hal_hw_interface/hal_pin_attrs.py index 126eef5..8b1bd99 100644 --- a/hal_hw_interface/src/hal_hw_interface/hal_pin_attrs.py +++ b/hal_hw_interface/src/hal_hw_interface/hal_pin_attrs.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -import hal +import machinekit.hal.cyhal as hal class HalPinAttrBase(int): diff --git a/hal_hw_interface/src/hal_hw_interface/tests/test_hal_pin_attrs.py b/hal_hw_interface/src/hal_hw_interface/tests/test_hal_pin_attrs.py index 5c7083c..c1f1b15 100644 --- a/hal_hw_interface/src/hal_hw_interface/tests/test_hal_pin_attrs.py +++ b/hal_hw_interface/src/hal_hw_interface/tests/test_hal_pin_attrs.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- import pytest -import hal +import machinekit.hal.cyhal as hal from hal_hw_interface.hal_pin_attrs import HalPinDir, HalPinType From 10ec5e5996d9e15dfcc653ee220a426ce69ef403 Mon Sep 17 00:00:00 2001 From: the-snowwhite Date: Tue, 10 Jan 2023 05:16:17 +0100 Subject: [PATCH 31/32] fix typo Signed-off-by: the-snowwhite --- hal_hw_interface/src/hal_hw_interface/hal_obj_base.py | 2 +- hal_hw_interface/src/hal_hw_interface/hal_pin_attrs.py | 2 +- .../src/hal_hw_interface/tests/test_hal_pin_attrs.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/hal_hw_interface/src/hal_hw_interface/hal_obj_base.py b/hal_hw_interface/src/hal_hw_interface/hal_obj_base.py index a12d6c3..8979a9a 100644 --- a/hal_hw_interface/src/hal_hw_interface/hal_obj_base.py +++ b/hal_hw_interface/src/hal_hw_interface/hal_obj_base.py @@ -7,7 +7,7 @@ .. moduleauthor:: John Morris """ -import machinekit.hal.cyhal as hal +import machinekit.hal.pyhal as hal import rospy diff --git a/hal_hw_interface/src/hal_hw_interface/hal_pin_attrs.py b/hal_hw_interface/src/hal_hw_interface/hal_pin_attrs.py index 8b1bd99..50933ce 100644 --- a/hal_hw_interface/src/hal_hw_interface/hal_pin_attrs.py +++ b/hal_hw_interface/src/hal_hw_interface/hal_pin_attrs.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -import machinekit.hal.cyhal as hal +import machinekit.hal.pyhal as hal class HalPinAttrBase(int): diff --git a/hal_hw_interface/src/hal_hw_interface/tests/test_hal_pin_attrs.py b/hal_hw_interface/src/hal_hw_interface/tests/test_hal_pin_attrs.py index c1f1b15..327ae96 100644 --- a/hal_hw_interface/src/hal_hw_interface/tests/test_hal_pin_attrs.py +++ b/hal_hw_interface/src/hal_hw_interface/tests/test_hal_pin_attrs.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- import pytest -import machinekit.hal.cyhal as hal +import machinekit.hal.pyhal as hal from hal_hw_interface.hal_pin_attrs import HalPinDir, HalPinType From 1943ccdc70e4306502c5280ea84c2c773e6f60b7 Mon Sep 17 00:00:00 2001 From: the-snowwhite Date: Tue, 10 Jan 2023 08:06:34 +0100 Subject: [PATCH 32/32] update readme Signed-off-by: the-snowwhite --- README.md | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 53b85db..414a402 100644 --- a/README.md +++ b/README.md @@ -25,7 +25,7 @@ extending [`ros_control_boilerplate`][ros_control_boilerplate]. The connecting HAL input and output pins with `std_msgs` publishers and subscribers, respectively. -[machinekit]: http://machinekit.io +[machinekit]: http://machinekit.io [ros_control_boilerplate]: https://github.com/davetcoleman/ros_control_boilerplate ## The `hal_hw_interface` real-time component @@ -65,14 +65,26 @@ and `UInt32` messages from ROS `std_msgs`. - A real-time kernel, either RT_PREEMPT or Xenomai - Required by Machinekit for low-latency control - See the `linux-image-rt-*` packages available in Debian Stretch. -- [`ros_control_boilerplate`][ros_control_boilerplate] +- [ros_control_boilerplate][ros_control_boilerplate] - Required by the `hal_hw_interface` - - This may be installed in package form. -- The `rrbot_description` package from `gazebo_ros_demos` +- [redis_store][redis_store] + - Required by the `hal_hw_interface` +- [redis_store_msgs][redis_store_msgs] + - Required by `redis_store` +- The `rrbot_description` package from [gazebo_ros_demos][gazebo_ros_demos] - Required by `ros_control_boilerplate` to run the `hal_rrbot_control` demo - Follow the notes in the `ros_control_boilerplate/README.md` to install this. +- Demo run additional dependencies: + - If complaints about missing libraries run `sudo ldconfig /opt/ros/noetic/lib/` + - add joint packages: `sudo apt install ros-noetic-joint-state-controller/focal ros-noetic-joint-trajectory-controller/focal` + - add missing python modules: `sudo pip install redis` `sudo pip install future` + - for halscope `sudo apt install machinekit-hal-unmanaged-components/focal` + +[redis_store]: https://github.com/machinekoder/redis_store/tree/noetic-devel/src/redis_store +[redis_store_msgs]: https://github.com/machinekoder/redis_store_msgs +[gazebo_ros_demos]: https://github.com/ros-simulation/gazebo_ros_demos ----- ## Run the demos