""" cellular.launch.py — Launch all saltybot_cellular nodes. Starts three nodes: gps_driver_node — NMEA GPS → /gps/fix, /gps/vel cellular_manager_node — AT modem monitor → /cellular/status, /cellular/rssi mqtt_bridge_node — ROS2 ↔ MQTT relay over cellular Prerequisites: 1. SIM7600X HAT connected via USB → /dev/ttyUSB0-2 must exist 2. Nano-SIM with active data plan inserted 3. NetworkManager profile "saltybot-cellular" configured (for nmcli method): nmcli connection add type gsm ifname '*' con-name saltybot-cellular apn Usage: # Defaults (all params from cellular_params.yaml): ros2 launch saltybot_cellular cellular.launch.py # GPS only (skip modem + MQTT): ros2 launch saltybot_cellular cellular.launch.py \ launch_cellular_manager:=false launch_mqtt_bridge:=false # Custom MQTT broker: ros2 launch saltybot_cellular cellular.launch.py mqtt_broker:=192.168.1.100 # Override params file: ros2 launch saltybot_cellular cellular.launch.py \ params_file:=/path/to/my_cellular_params.yaml GPS topics: /gps/fix (sensor_msgs/NavSatFix) /gps/vel (geometry_msgs/TwistStamped) Cellular topics: /cellular/status (diagnostic_msgs/DiagnosticArray) /cellular/rssi (std_msgs/Int32) /cellular/connected (std_msgs/Bool) MQTT relay (ROS2→MQTT): saltybot/imu, saltybot/gps/fix, saltybot/gps/vel, saltybot/uwb/ranges, saltybot/person, saltybot/cellular MQTT relay (MQTT→ROS2): saltybot/cmd → /saltybot/cmd, saltybot/estop → /saltybot/estop """ import os from ament_index_python.packages import get_package_share_directory from launch import LaunchDescription from launch.actions import DeclareLaunchArgument, OpaqueFunction from launch.conditions import IfCondition from launch.substitutions import LaunchConfiguration from launch_ros.actions import Node def _launch_nodes(context, *args, **kwargs): params_file = LaunchConfiguration("params_file").perform(context) def _b(name): return LaunchConfiguration(name).perform(context).lower() == "true" def _s(name): return LaunchConfiguration(name).perform(context) def _f(name): return float(LaunchConfiguration(name).perform(context)) def _i(name): return int(LaunchConfiguration(name).perform(context)) gps_params = { "gps_port": _s("gps_port"), "at_port": _s("at_port"), "baud_rate": _i("baud_rate"), "publish_rate": _f("gps_publish_rate"), "frame_id": _s("frame_id"), "gps_init_enable": _b("gps_init_enable"), "gps_init_cmd": _s("gps_init_cmd"), "gps_accuracy": _f("gps_accuracy"), } cellular_params = { "at_port": _s("at_port"), "baud_rate": _i("baud_rate"), "poll_rate": _f("cellular_poll_rate"), "connection_method": _s("connection_method"), "nmcli_profile": _s("nmcli_profile"), "apn": _s("apn"), "reconnect_interval": _f("reconnect_interval"), } mqtt_params = { "mqtt_broker": _s("mqtt_broker"), "mqtt_port": _i("mqtt_port"), "mqtt_user": _s("mqtt_user"), "mqtt_password": _s("mqtt_password"), "topic_prefix": _s("topic_prefix"), "tls_enable": _b("tls_enable"), "tls_ca_cert": _s("tls_ca_cert"), "imu_rate": _f("imu_rate"), "gps_rate": _f("gps_rate"), "person_rate": _f("person_rate"), "uwb_rate": _f("uwb_rate"), } def _node_params(inline): return [params_file, inline] if params_file else [inline] nodes = [ Node( package="saltybot_cellular", executable="gps_driver_node", name="gps_driver", output="screen", parameters=_node_params(gps_params), ), ] if _b("launch_cellular_manager"): nodes.append(Node( package="saltybot_cellular", executable="cellular_manager_node", name="cellular_manager", output="screen", parameters=_node_params(cellular_params), )) if _b("launch_mqtt_bridge"): nodes.append(Node( package="saltybot_cellular", executable="mqtt_bridge_node", name="mqtt_bridge", output="screen", parameters=_node_params(mqtt_params), )) return nodes def generate_launch_description(): pkg_share = get_package_share_directory("saltybot_cellular") default_params = os.path.join(pkg_share, "config", "cellular_params.yaml") return LaunchDescription([ DeclareLaunchArgument( "params_file", default_value=default_params, description="Full path to cellular_params.yaml (overrides inline args)"), # Node enable/disable DeclareLaunchArgument( "launch_cellular_manager", default_value="true", description="Launch cellular_manager_node"), DeclareLaunchArgument( "launch_mqtt_bridge", default_value="true", description="Launch mqtt_bridge_node"), # GPS driver DeclareLaunchArgument("gps_port", default_value="/dev/ttyUSB2"), DeclareLaunchArgument("at_port", default_value="/dev/ttyUSB0"), DeclareLaunchArgument("baud_rate", default_value="115200"), DeclareLaunchArgument("gps_publish_rate", default_value="1.0", description="GPS publish rate (Hz)"), DeclareLaunchArgument("frame_id", default_value="gps"), DeclareLaunchArgument("gps_init_enable", default_value="true", description="Send AT+CGPS=1 on startup"), DeclareLaunchArgument("gps_init_cmd", default_value="AT+CGPS=1"), DeclareLaunchArgument("gps_accuracy", default_value="2.5", description="GPS CEP accuracy at HDOP=1 (m)"), # Cellular manager DeclareLaunchArgument("cellular_poll_rate", default_value="0.2", description="AT poll rate (Hz)"), DeclareLaunchArgument("connection_method", default_value="nmcli", description="nmcli | pppd | none"), DeclareLaunchArgument("nmcli_profile", default_value="saltybot-cellular"), DeclareLaunchArgument("apn", default_value=""), DeclareLaunchArgument("reconnect_interval", default_value="30.0"), # MQTT bridge DeclareLaunchArgument("mqtt_broker", default_value="mqtt.saltylab.local"), DeclareLaunchArgument("mqtt_port", default_value="1883"), DeclareLaunchArgument("mqtt_user", default_value=""), DeclareLaunchArgument("mqtt_password", default_value=""), DeclareLaunchArgument("topic_prefix", default_value="saltybot"), DeclareLaunchArgument("tls_enable", default_value="false"), DeclareLaunchArgument("tls_ca_cert", default_value=""), DeclareLaunchArgument("imu_rate", default_value="5.0"), DeclareLaunchArgument("gps_rate", default_value="1.0"), DeclareLaunchArgument("person_rate", default_value="2.0"), DeclareLaunchArgument("uwb_rate", default_value="1.0"), OpaqueFunction(function=_launch_nodes), ])