Skip to content

Add hw and os checks #491

New issue

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

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

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Feb 10, 2025
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
100 changes: 94 additions & 6 deletions husarion_ugv_bringup/launch/bringup.launch.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,20 +15,67 @@
# limitations under the License.


from husarion_ugv_utils.messages import welcome_msg
import os
import re

from husarion_ugv_utils.messages import (
ErrorMessages,
error_msg,
warning_msg,
welcome_msg,
)
from launch import LaunchDescription
from launch.actions import DeclareLaunchArgument, IncludeLaunchDescription, TimerAction
from launch.conditions import UnlessCondition
from launch.actions import (
DeclareLaunchArgument,
ExecuteProcess,
GroupAction,
IncludeLaunchDescription,
TimerAction,
)
from launch.conditions import IfCondition, UnlessCondition
from launch.launch_description_sources import PythonLaunchDescriptionSource
from launch.substitutions import (
EnvironmentVariable,
LaunchConfiguration,
PathJoinSubstitution,
PythonExpression,
)
from launch_ros.substitutions import FindPackageShare

MIN_REQUIRED_OS_VERSION = [2, 2, 0]


def check_os_version_compatibility(version_string: str, min_required_version: list[int]) -> bool:
match = re.search(r"v(\d+\.\d+\.\d+)", version_string)

if not match:
return False

version_str = match.group(1)
version = version_str.split(".")

if int(version[0]) > min_required_version[0]:
return True

if int(version[0]) == min_required_version[0]:
if int(version[1]) > min_required_version[1]:
return True

if int(version[1]) == min_required_version[1]:
return int(version[2]) >= min_required_version[2]

return False
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Propose to move it to *_utils and simplify it. Like in following example

import re

MIN_REQUIRED_OS_VERSION = "v2.2.2"


def extract_version_tuple(version_string: str) -> tuple[int, int, int]:
    match = re.search(r"v(\d+)\.(\d+)\.(\d+)", version_string)
    return tuple(map(int, match.groups())) if match else (0, 0, 0)

def check_os_version_compatibility(version1: str, min_required_version: str) -> bool:
    return extract_version_tuple(version1) >= extract_version_tuple(min_required_version)

# Example usage
print(check_os_version_compatibility("v1.9.5", MIN_REQUIRED_OS_VERSION))  # False
print(check_os_version_compatibility("v2.2.2", MIN_REQUIRED_OS_VERSION))  # True
print(check_os_version_compatibility("v2.3.0", MIN_REQUIRED_OS_VERSION))  # True

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That is smart



def generate_launch_description():
exit_on_wrong_hw = LaunchConfiguration("exit_on_wrong_hw")
declare_exit_on_wrong_hw_arg = DeclareLaunchArgument(
"exit_on_wrong_hw",
default_value="false",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not default true?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's the debate what is our default state. I assumed using docker and avoiding unnecessary restarts of it

description="Exit if hardware configuration is incorrect.",
choices=["True", "true", "False", "false"],
)

common_dir_path = LaunchConfiguration("common_dir_path")
declare_common_dir_path_arg = DeclareLaunchArgument(
"common_dir_path",
Expand Down Expand Up @@ -139,6 +186,37 @@ def generate_launch_description():
}.items(),
)

hw_config_correct = EnvironmentVariable(name="ROBOT_HW_CONFIG_CORRECT", default_value="false")

prevent_exit_action = ExecuteProcess(
cmd=["sleep", "infinity"],
condition=UnlessCondition(exit_on_wrong_hw),
)

incorrect_hw_config_action = GroupAction(
actions=[
error_msg(ErrorMessages.INCORRECT_HW_CONFIG),
prevent_exit_action,
],
condition=UnlessCondition(hw_config_correct),
)

os_version = os.environ.get("SYSTEM_BUILD_VERSION", "v0.0.0")
os_version_correct = PythonExpression(
f"{check_os_version_compatibility(os_version, MIN_REQUIRED_OS_VERSION)}"
)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PythonExpression probably can be omitted, because os.environ is just a string

Suggested change
os_version_correct = PythonExpression(
f"{check_os_version_compatibility(os_version, MIN_REQUIRED_OS_VERSION)}"
)
os_version_correct = check_os_version_compatibility(os_version, MIN_REQUIRED_OS_VERSION)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

UnlessCondition doesn't accept bool, that's why this is wrapped with PythonExpression


incorrect_os_version_action = GroupAction(
[
warning_msg(
ErrorMessages.INCORRECT_OS_VERSION
+ f"Current version: {os_version},"
+ f" required: v{MIN_REQUIRED_OS_VERSION[0]}.{MIN_REQUIRED_OS_VERSION[1]}.{MIN_REQUIRED_OS_VERSION[2]}\n"
)
],
condition=UnlessCondition(os_version_correct),
)

delayed_action = TimerAction(
period=10.0,
actions=[
Expand All @@ -149,15 +227,25 @@ def generate_launch_description():
],
)

driver_actions = GroupAction(
[
controller_launch,
system_monitor_launch,
delayed_action,
],
condition=IfCondition(hw_config_correct),
)

actions = [
declare_exit_on_wrong_hw_arg,
declare_common_dir_path_arg,
declare_disable_manager_arg,
declare_log_level_arg,
declare_namespace_arg,
welcome_info,
controller_launch,
system_monitor_launch,
delayed_action,
incorrect_hw_config_action,
incorrect_os_version_action,
driver_actions,
]

return LaunchDescription(actions)
28 changes: 28 additions & 0 deletions husarion_ugv_utils/husarion_ugv_utils/messages.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,24 @@
PANTHER_TEXT = click.style(textwrap.dedent(PANTHER_ASCII), bold=True)


class ErrorMessages:
INCORRECT_HW_CONFIG = textwrap.dedent(
r"""

ERROR: Incorrect hardware configuration detected. ROS nodes are prevented from starting!
Refer to instructions in manual or those shown on terminal login.
"""
)

INCORRECT_OS_VERSION = textwrap.dedent(
r"""

WARNING: Unsupported OS version detected. ROS diver may not work correctly.
Please update your system to the latest version.
"""
)


def flatten(lst):
"""Flatten a nested list into a single list."""
if isinstance(lst, list):
Expand Down Expand Up @@ -89,3 +107,13 @@ def welcome_msg(
stats_msg.insert(0, robot_model_expr)

return LogInfo(msg=stats_msg)


def error_msg(error: str):
"""Generate an error message."""
return LogInfo(msg=click.style(error, bold=True, fg="red"))


def warning_msg(warning: str):
"""Generate a warning message."""
return LogInfo(msg=click.style(warning, bold=True, fg="yellow"))