diff --git a/controller_manager/controller_manager/controller_manager_services.py b/controller_manager/controller_manager/controller_manager_services.py index dc2647993f..9239dee271 100644 --- a/controller_manager/controller_manager/controller_manager_services.py +++ b/controller_manager/controller_manager/controller_manager_services.py @@ -38,17 +38,30 @@ from ros2param.api import call_set_parameters -# from https://stackoverflow.com/a/287944 +import os +import sys + + +def _color_enabled(): + """Respect RCUTILS_COLORIZED_OUTPUT: 0=off, 1=on, unset=auto-detect TTY.""" + env = os.getenv("RCUTILS_COLORIZED_OUTPUT") + if env == "0": + return False + if env == "1": + return True + return sys.stdout.isatty() + + class bcolors: - MAGENTA = "\033[95m" - OKBLUE = "\033[94m" - OKCYAN = "\033[96m" - OKGREEN = "\033[92m" - WARNING = "\033[93m" - FAIL = "\033[91m" - ENDC = "\033[0m" - BOLD = "\033[1m" - UNDERLINE = "\033[4m" + MAGENTA = "\033[95m" if _color_enabled() else "" + OKBLUE = "\033[94m" if _color_enabled() else "" + OKCYAN = "\033[96m" if _color_enabled() else "" + OKGREEN = "\033[92m" if _color_enabled() else "" + WARNING = "\033[93m" if _color_enabled() else "" + FAIL = "\033[91m" if _color_enabled() else "" + ENDC = "\033[0m" if _color_enabled() else "" + BOLD = "\033[1m" if _color_enabled() else "" + UNDERLINE = "\033[4m" if _color_enabled() else "" class ServiceNotFoundError(Exception): diff --git a/controller_manager/controller_manager/hardware_spawner.py b/controller_manager/controller_manager/hardware_spawner.py index abba851432..07df80af02 100644 --- a/controller_manager/controller_manager/hardware_spawner.py +++ b/controller_manager/controller_manager/hardware_spawner.py @@ -19,9 +19,8 @@ from controller_manager import ( list_hardware_components, set_hardware_component_state, - bcolors, ) -from controller_manager.controller_manager_services import ServiceNotFoundError +from controller_manager.controller_manager_services import ServiceNotFoundError, bcolors from lifecycle_msgs.msg import State import rclpy @@ -93,7 +92,11 @@ def configure_component(node, controller_manager_name, component_to_configure): inactive_state.id = State.PRIMARY_STATE_INACTIVE inactive_state.label = "inactive" handle_set_component_state_service_call( - node, controller_manager_name, component_to_configure, inactive_state, "configured" + node, + controller_manager_name, + component_to_configure, + inactive_state, + "configured", ) @@ -154,7 +157,10 @@ def main(args=None): try: for hardware_component in hardware_components: if not is_hardware_component_loaded( - node, controller_manager_name, hardware_component, controller_manager_timeout + node, + controller_manager_name, + hardware_component, + controller_manager_timeout, ): node.get_logger().warning( f"{bcolors.WARNING}Hardware Component is not loaded - state can not be changed.{bcolors.ENDC}" diff --git a/controller_manager/controller_manager/spawner.py b/controller_manager/controller_manager/spawner.py index 38628d0af8..e3fdb1f128 100644 --- a/controller_manager/controller_manager/spawner.py +++ b/controller_manager/controller_manager/spawner.py @@ -28,10 +28,9 @@ unload_controller, set_controller_parameters, set_controller_parameters_from_param_files, - bcolors, ) from controller_manager_msgs.srv import SwitchController -from controller_manager.controller_manager_services import ServiceNotFoundError +from controller_manager.controller_manager_services import ServiceNotFoundError, bcolors from filelock import Timeout, FileLock import rclpy @@ -51,7 +50,8 @@ def combine_name_and_namespace(name_and_namespace): def find_node_and_namespace(node, full_node_name): node_names_and_namespaces = node.get_node_names_and_namespaces() return first_match( - node_names_and_namespaces, lambda n: combine_name_and_namespace(n) == full_node_name + node_names_and_namespaces, + lambda n: combine_name_and_namespace(n) == full_node_name, ) diff --git a/controller_manager/doc/userdoc.rst b/controller_manager/doc/userdoc.rst index b5566b72a7..a2aaf766d9 100644 --- a/controller_manager/doc/userdoc.rst +++ b/controller_manager/doc/userdoc.rst @@ -494,3 +494,14 @@ The ``time`` argument in the ``read`` and ``write`` methods of the hardware comp The ``period`` argument in the ``read``, ``update`` and ``write`` methods is calculated using the trigger clock of type ``RCL_STEADY_TIME`` so it is always monotonic. The reason behind using different clocks is to avoid the issues related to the affect of system time changes in the realtime loops. The ``ros2_control_node`` now also detects the overruns caused by the system time changes and longer execution times of the controllers and hardware components. The controller manager will print a warning message if the controller or hardware component misses the update cycle due to the system time changes or longer execution times. + +Color Output Handling +^^^^^^^^^^^^^^^^^^^^^ + +The helper scripts (``spawner`` and ``hardware_spawner``) now use an environment-aware ``bcolors`` class. +The color output automatically adapts to the environment: + +* ``RCUTILS_COLORIZED_OUTPUT=0`` -> disables color output +* ``RCUTILS_COLORIZED_OUTPUT=1`` -> forces color output +* Unset -> automatically detects TTY and enables color only in interactive + terminals diff --git a/doc/release_notes.rst b/doc/release_notes.rst index 5f031f15ba..fd450cd04e 100644 --- a/doc/release_notes.rst +++ b/doc/release_notes.rst @@ -12,6 +12,8 @@ controller_interface controller_manager ****************** +* The ``bcolors`` class now respects the ``RCUTILS_COLORIZED_OUTPUT`` environment + variable to automatically disable colors in non-TTY and CI environments. * The default strictness for ``switch_controller`` is changed to ``strict``. (`#2742 `__) @@ -20,6 +22,9 @@ hardware_interface ros2controlcli ************** + +No notable changes in this release. + transmission_interface ********************** * The ``simple_transmission`` and ``differential_transmission`` now also support the ``force`` interface (`#2588 `_).