diff --git a/CMakeLists.txt b/CMakeLists.txt
deleted file mode 100644
index c493202d..00000000
--- a/CMakeLists.txt
+++ /dev/null
@@ -1,57 +0,0 @@
-cmake_minimum_required(VERSION 3.5)
-project(hw_device_mgr)
-
-# Find dependencies
-find_package(ament_cmake REQUIRED)
-# - RT
-find_package(hal_hw_interface REQUIRED)
-# - Python
-find_package(ament_cmake_python REQUIRED)
-find_package(rclpy REQUIRED)
-# - Msg
-find_package(rosidl_default_generators REQUIRED)
-
-#***********************************************
-#* Declare ROS messages, services and actions **
-#***********************************************
-
-rosidl_generate_interfaces(${PROJECT_NAME}
- msg/MsgError.msg
- ADD_LINTER_TESTS
-)
-
-#************
-#* Install **
-#************
-
-# Install python modules
-ament_python_install_package(${PROJECT_NAME})
-
-#**********
-#* Tests **
-#**********
-
-if(BUILD_TESTING)
-
- find_package(ament_cmake_flake8 REQUIRED)
- ament_flake8()
- find_package(ament_cmake_lint_cmake REQUIRED)
- ament_lint_cmake()
- # FIXME disable until this is resolved, either by loading the xsd
- # file or by moving the ESI files out of this repo or by excluding
- # ESI files from the linter.
- #
- # warning: failed to load external entity "EtherCATInfo.xsd"
- # Schemas parser error : Failed to locate the main schema resource at 'EtherCATInfo.xsd'.
- # WXS schema EtherCATInfo.xsd failed to compile
- #
- # find_package(ament_cmake_xmllint REQUIRED)
- # ament_xmllint()
- find_package(ament_cmake_pytest REQUIRED)
- ament_add_pytest_test(test_modules "hw_device_mgr" TIMEOUT 120)
-
-endif()
-
-ament_export_dependencies(rosidl_default_runtime)
-
-ament_package()
diff --git a/hw_device_mgr/devices/device_xml/__init__.py b/hw_device_mgr/devices/device_xml/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/hw_device_mgr/logging.py b/hw_device_mgr/logging.py
deleted file mode 100644
index 83531aac..00000000
--- a/hw_device_mgr/logging.py
+++ /dev/null
@@ -1,33 +0,0 @@
-from rclpy import logging
-
-
-class Logging:
- """Wrapper for `rclpy.logging` object."""
-
- def __init__(self, name):
- self._logger = logging.get_logger(name)
-
- # Translate Python str levels to rclpy str levels (int levels are
- # the same)
- _rclpy_level_map = dict(
- critical="fatal",
- error="error",
- warning="warn",
- info="info",
- debug="debug",
- notset="unset",
- )
-
- def setLevel(self, level):
- if isinstance(level, str):
- level = self._rclpy_level_map.get(level.upper(), level).upper()
- self._logger.set_logger_level(level)
-
- def __getattr__(self, name):
- if name in self._rclpy_level_map:
- return getattr(self._logger, self._rclpy_level_map[name])
- raise AttributeError(f"'Logging' object has no attribute '{name}'")
-
- @classmethod
- def getLogger(cls, name):
- return cls(name)
diff --git a/hw_device_mgr/logging/__init__.py b/hw_device_mgr/logging/__init__.py
new file mode 100644
index 00000000..1ef29588
--- /dev/null
+++ b/hw_device_mgr/logging/__init__.py
@@ -0,0 +1,45 @@
+import logging
+
+
+class Logging:
+ """Wrapper for `logging` module."""
+
+ _logging_class = logging
+
+ def __init__(self, name):
+ lc = self._logging_class
+ lo = self._logger = lc.getLogger(name)
+ lh = self._log_handler = lc.StreamHandler()
+ lf = lc.Formatter("%(asctime)s [%(levelname)s]%(name)s: %(message)s")
+ lh.setFormatter(lf)
+ lo.addHandler(lh)
+
+ # Translate Python str levels to str levels (int levels are the same)
+ _level_map = dict(
+ fatal="fatal",
+ error="error",
+ warning="warning",
+ info="info",
+ debug="debug",
+ notset="notset",
+ )
+
+ def setLevel(self, level):
+ if isinstance(level, str):
+ level = self._level_map.get(level.upper(), level).upper()
+ self._logger.setLevel(level)
+
+ def getLevel(self):
+ return self._logger.getEffectiveLevel()
+
+ def __getattr__(self, name):
+ if name in self._level_map:
+ return getattr(self._logger, self._level_map[name])
+ if name.lower() in self._level_map:
+ attr = self._level_map[name.lower()].upper()
+ return getattr(self._logging_class, attr)
+ raise AttributeError(f"'Logging' object has no attribute '{name}'")
+
+ @classmethod
+ def getLogger(cls, name):
+ return cls(name)
diff --git a/hw_device_mgr/logging/ros.py b/hw_device_mgr/logging/ros.py
new file mode 100644
index 00000000..c04ddc44
--- /dev/null
+++ b/hw_device_mgr/logging/ros.py
@@ -0,0 +1,30 @@
+from rclpy import logging
+from . import Logging
+from rclpy.logging import LoggingSeverity
+
+
+class ROSLogging(Logging):
+ """Wrapper for `rclpy.logging` object."""
+
+ def __init__(self, name):
+ self._logger = logging.get_logger(name)
+
+ # Translate Python str levels to rclpy str levels (int levels are
+ # the same)
+ _rclpy_level_map = dict(
+ critical=LoggingSeverity.FATAL,
+ error=LoggingSeverity.ERROR,
+ warning=LoggingSeverity.WARN,
+ info=LoggingSeverity.INFO,
+ debug=LoggingSeverity.DEBUG,
+ notset=LoggingSeverity.UNSET,
+ )
+
+ def setLevel(self, level):
+ if isinstance(level, str):
+ level = self._rclpy_level_map.get(level.lower(), None)
+ assert level is not None, f"Invalid log level '{level}'"
+ self._logger.set_level(level)
+
+ def getLevel(self):
+ return self._logger.get_effective_level()
diff --git a/hw_device_mgr/logging/tests/__init__.py b/hw_device_mgr/logging/tests/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/hw_device_mgr/logging/tests/test_logging.py b/hw_device_mgr/logging/tests/test_logging.py
new file mode 100644
index 00000000..2bdf45d1
--- /dev/null
+++ b/hw_device_mgr/logging/tests/test_logging.py
@@ -0,0 +1,21 @@
+import pytest
+from .. import Logging
+
+
+class TestLogging:
+ tc = Logging
+
+ @pytest.fixture
+ def obj(self):
+ return self.tc("test")
+
+ def test_init(self, obj):
+ assert hasattr(obj, "_logger")
+
+ def test_getLevel_setLevel(self, obj):
+ obj.setLevel("error")
+ assert obj.getLevel() == obj.ERROR
+
+ def test_getLogger(self):
+ logger = self.tc.getLogger("test")
+ assert isinstance(logger, self.tc)
diff --git a/hw_device_mgr/logging/tests/test_ros_logging.py b/hw_device_mgr/logging/tests/test_ros_logging.py
new file mode 100644
index 00000000..030dcbf7
--- /dev/null
+++ b/hw_device_mgr/logging/tests/test_ros_logging.py
@@ -0,0 +1,13 @@
+import pytest
+
+try:
+ import rclpy # noqa: F401
+except ModuleNotFoundError:
+ pytest.skip(allow_module_level=True)
+
+from ..ros import ROSLogging
+from .test_logging import TestLogging as _TestLogging
+
+
+class TestROSLogging(_TestLogging):
+ tc = ROSLogging
diff --git a/hw_device_mgr/mgr/mgr.py b/hw_device_mgr/mgr/mgr.py
index 1948d811..5d30c4ad 100644
--- a/hw_device_mgr/mgr/mgr.py
+++ b/hw_device_mgr/mgr/mgr.py
@@ -47,8 +47,6 @@ def device_model_id(cls):
command_out_defaults = dict(state_cmd=0, reset=0)
command_out_data_types = dict(state_cmd="uint8", reset="bit")
- update_rate = 10 # Hz
-
####################################################
# Initialization
@@ -406,6 +404,7 @@ def fsm_finalize_command(self, e):
def run(self):
"""Program main loop."""
+ update_period = 1.0 / self.mgr_config.get("update_rate", 10.0)
while not self.shutdown:
try:
self.read_update_write()
@@ -423,7 +422,7 @@ def run(self):
# the `sleep()` before the next update
self.fast_track = False
continue
- self.rate.sleep()
+ time.sleep(update_period)
def read_update_write(self):
"""
diff --git a/hw_device_mgr/mgr/tests/bogus_devices/mgr_config.yaml b/hw_device_mgr/mgr/tests/bogus_devices/mgr_config.yaml
index 9aae8a01..836ba8cb 100644
--- a/hw_device_mgr/mgr/tests/bogus_devices/mgr_config.yaml
+++ b/hw_device_mgr/mgr/tests/bogus_devices/mgr_config.yaml
@@ -1,5 +1,6 @@
init_timeout: 30.0 # seconds
goal_state_timeout: 5.0 # seconds
+update_rate: 20.0 # updates/second
devices:
# 6 DOF joints
diff --git a/hw_device_mgr/mgr_ros/tests/base_test_class.py b/hw_device_mgr/mgr_ros/tests/base_test_class.py
index 560e22de..f2929c46 100644
--- a/hw_device_mgr/mgr_ros/tests/base_test_class.py
+++ b/hw_device_mgr/mgr_ros/tests/base_test_class.py
@@ -1,8 +1,13 @@
from ...mgr.tests.base_test_class import BaseMgrTestClass
-from .bogus_devices.mgr import ROSHWDeviceMgrTest
import yaml
import pytest
+try:
+ import rclpy # noqa: F401
+except ModuleNotFoundError:
+ pytest.skip(allow_module_level=True)
+from .bogus_devices.mgr import ROSHWDeviceMgrTest
+
###############################
# Test class
@@ -75,7 +80,7 @@ def device_config_path(self, tmp_path, device_config, mock_rclpy):
yield tmpfile
def test_mock_rclpy_fixture(self, mock_rclpy):
- from ..mgr import rclpy
+ from ..mgr import rclpy # noqa: F811
node = rclpy.create_node("foo")
assert node is self.node
diff --git a/package.xml b/package.xml
index 4a1fe168..765fb57b 100644
--- a/package.xml
+++ b/package.xml
@@ -24,6 +24,9 @@
python-fysom
python3-lxml
+ ament_copyright
+ ament_flake8
+ ament_pep257
python3-pytest
python3-pytest-cov
python3-pytest-mock
@@ -31,6 +34,6 @@
rosidl_interface_packages
- ament_cmake
+ ament_python
diff --git a/resource/hw_device_mgr b/resource/hw_device_mgr
new file mode 100644
index 00000000..e69de29b
diff --git a/setup.py b/setup.py
index c23a1e4d..fc43bfce 100644
--- a/setup.py
+++ b/setup.py
@@ -16,7 +16,11 @@
"mgr_ros_hal",
]
# Packages like hw_device_mgr.{pkg}.tests
-pkgs_t = ["devices"] + pkgs_bd
+pkgs_t = [
+ "devices",
+ "logging",
+ *pkgs_bd,
+]
# Generate lists
packages = (
[