<?xml version="1.0"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
	<id>https://rsewiki.electro.dtu.dk/api.php?action=feedcontributions&amp;feedformat=atom&amp;user=S252724</id>
	<title>Rsewiki - User contributions [en]</title>
	<link rel="self" type="application/atom+xml" href="https://rsewiki.electro.dtu.dk/api.php?action=feedcontributions&amp;feedformat=atom&amp;user=S252724"/>
	<link rel="alternate" type="text/html" href="https://rsewiki.electro.dtu.dk/index.php?title=Special:Contributions/S252724"/>
	<updated>2026-06-11T13:31:58Z</updated>
	<subtitle>User contributions</subtitle>
	<generator>MediaWiki 1.41.1</generator>
	<entry>
		<id>https://rsewiki.electro.dtu.dk/index.php?title=Fejemis_Maploc_Ros2&amp;diff=8983</id>
		<title>Fejemis Maploc Ros2</title>
		<link rel="alternate" type="text/html" href="https://rsewiki.electro.dtu.dk/index.php?title=Fejemis_Maploc_Ros2&amp;diff=8983"/>
		<updated>2026-05-28T11:41:40Z</updated>

		<summary type="html">&lt;p&gt;S252724: /* Cleaning */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Back to [[Fejemis ROS2 Software]]&lt;br /&gt;
&lt;br /&gt;
== Purpose ==&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fejemis_maploc&#039;&#039; is the mapping, localisation, navigation, and behaviour package for Fejemis. It connects the robot&#039;s sensor data to the navigation stack and exposes the high-level behaviour tree used to run calibration, mapping, waypoint navigation, and cleaning missions.&lt;br /&gt;
&lt;br /&gt;
The package sits between the low-level bridge and the high-level mission logic:&lt;br /&gt;
&lt;br /&gt;
* The bridge publishes wheel odometry, IMU data, and accepts velocity / brush commands.&lt;br /&gt;
* &#039;&#039;fejemis_maploc&#039;&#039; filters odometry, starts LiDAR and RealSense sensing, runs RTAB-Map, starts Nav2, and runs the Fejemis behaviour tree.&lt;br /&gt;
* The behaviour tree sends commands to Nav2, the coverage planner, the mixer, and the brush service.&lt;br /&gt;
[[File:maploc_general_block_diagram.png| 1600px]]&lt;br /&gt;
&lt;br /&gt;
Overall &#039;&#039;fejemis_maploc&#039;&#039; data flow from bridge, LiDAR, and RealSense into odometry, RTAB-Map, Nav2, coverage planning, behaviour tree, and robot commands.&lt;br /&gt;
&lt;br /&gt;
== Package Layout ==&lt;br /&gt;
&lt;br /&gt;
Important package folders:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;resources/launch&#039;&#039; - ROS 2 launch files for sensors, odometry, SLAM/localisation, planning, lifecycle managers, and behaviour trees.&lt;br /&gt;
* &#039;&#039;resources/config&#039;&#039; - YAML configuration for odometry, Nav2, RTAB-Map, RealSense, lifecycle managers, and BT plugins.&lt;br /&gt;
* &#039;&#039;resources/behavior&#039;&#039; - behaviour tree XML files. &#039;&#039;main.xml&#039;&#039; is the older BT.CPP format and &#039;&#039;main4.xml&#039;&#039; is the BT.CPP v4 version used on newer ROS distributions.&lt;br /&gt;
* &#039;&#039;scripts&#039;&#039; - Python helper nodes for mission triggering, RViz goal relays, brush services, velocity conversion, and coverage tests.&lt;br /&gt;
* &#039;&#039;src/bt_plugins&#039;&#039; - C++ behaviour tree plugin used by the cleaning behaviour.&lt;br /&gt;
&lt;br /&gt;
== Main Topics and Frames ==&lt;br /&gt;
&lt;br /&gt;
Common frame names:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;map&#039;&#039; - global map frame produced by RTAB-Map / Nav2.&lt;br /&gt;
* &#039;&#039;odom&#039;&#039; - local odometry frame.&lt;br /&gt;
* &#039;&#039;base_link&#039;&#039; - robot base frame.&lt;br /&gt;
* &#039;&#039;lidar_link&#039;&#039; - LiDAR frame.&lt;br /&gt;
* &#039;&#039;camera_camera_link&#039;&#039; and RealSense optical frames - camera frames used by the RealSense driver and RTAB-Map.&lt;br /&gt;
&lt;br /&gt;
Important topics and actions:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;/fejemis/odom&#039;&#039; - wheel odometry from the bridge.&lt;br /&gt;
* &#039;&#039;/fejemis/imu&#039;&#039; and &#039;&#039;/fejemis/mag&#039;&#039; - raw IMU and magnetometer inputs.&lt;br /&gt;
* &#039;&#039;/loc/imu&#039;&#039; - filtered IMU output from Madgwick.&lt;br /&gt;
* &#039;&#039;/loc/odom&#039;&#039; - fused odometry output from the EKF.&lt;br /&gt;
* &#039;&#039;/fejemis/scan&#039;&#039; - LiDAR scan.&lt;br /&gt;
* &#039;&#039;/camera/color/image_raw&#039;&#039;, &#039;&#039;/camera/depth/image_rect_raw&#039;&#039;, and camera info topics - RealSense RGB-D input.&lt;br /&gt;
* &#039;&#039;/camera_scan&#039;&#039; - depth-image-to-laserscan output used as an extra obstacle source.&lt;br /&gt;
* &#039;&#039;/loc/map&#039;&#039; - occupancy map used by Nav2.&lt;br /&gt;
* &#039;&#039;/control/nav2/cmd_vel&#039;&#039; - Nav2 velocity command before Fejemis-specific conversion.&lt;br /&gt;
* &#039;&#039;/control/joy/cmd_vel&#039;&#039; - velocity command sent into the existing Fejemis control path.&lt;br /&gt;
* &#039;&#039;/behavior/set_mission&#039;&#039; - mission command topic consumed by the behaviour tree.&lt;br /&gt;
* &#039;&#039;/blackboard/...&#039;&#039; - blackboard values mirrored into the behaviour tree.&lt;br /&gt;
* &#039;&#039;/fejemis/navigate_to_pose&#039;&#039; - Nav2 action used for waypoint goals.&lt;br /&gt;
* &#039;&#039;/fejemis/navigate_complete_coverage&#039;&#039; - OpenNav coverage action used for coverage missions.&lt;br /&gt;
* &#039;&#039;/control/brush/controller/on&#039;&#039; and &#039;&#039;/control/brush/controller/off&#039;&#039; - brush control trigger services.&lt;br /&gt;
&lt;br /&gt;
== Launch Files ==&lt;br /&gt;
&lt;br /&gt;
The package is designed so the top-level &#039;&#039;fejemis/launch/robot.launch.py&#039;&#039; can include only the parts needed on each Raspberry Pi. The main Pi typically handles bridge control and odometry, while the auxiliary Pi handles heavier sensing such as LiDAR, RealSense, RTAB-Map, and vision.&lt;br /&gt;
&lt;br /&gt;
=== launch/lidar.launch.py ===&lt;br /&gt;
&lt;br /&gt;
Starts the LD19 LiDAR driver from &#039;&#039;ldlidar_stl_ros2&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
Launch arguments:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;ld_make_container&#039;&#039; - whether the LiDAR launch should create a component container. Default: &#039;&#039;True&#039;&#039;.&lt;br /&gt;
* &#039;&#039;ld_container&#039;&#039; - component container name. Default: &#039;&#039;_laser&#039;&#039;.&lt;br /&gt;
* &#039;&#039;ld_container_ns&#039;&#039; - namespace for the component container. Default: &#039;&#039;loc&#039;&#039;.&lt;br /&gt;
* &#039;&#039;port_name&#039;&#039; - serial port for the LiDAR. Default: &#039;&#039;/dev/ttyUSB0&#039;&#039;.&lt;br /&gt;
* &#039;&#039;namespace&#039;&#039; - launch argument declared for compatibility. The included launch currently receives &#039;&#039;fejemis&#039;&#039; as namespace.&lt;br /&gt;
&lt;br /&gt;
Important configuration passed to the driver:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;laser_frame=lidar_link&#039;&#039;&lt;br /&gt;
* &#039;&#039;clockwise_dir=false&#039;&#039;&lt;br /&gt;
* &#039;&#039;range_min=0.2&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The resulting scan is used by RTAB-Map and by the Nav2 costmaps as the main 2D obstacle source.&lt;br /&gt;
&lt;br /&gt;
=== launch/tf.launch.py ===&lt;br /&gt;
&lt;br /&gt;
Publishes static transforms for the physical sensor layout.&lt;br /&gt;
&lt;br /&gt;
Published transforms:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;base_link -&amp;gt; camera_camera_link&#039;&#039; at approximately x=-0.24 m, y=0.0 m, z=0.44 m.&lt;br /&gt;
* On the real robot, &#039;&#039;base_link -&amp;gt; lidar_link&#039;&#039; at approximately x=-0.29 m, y=0.0 m, z=0.25 m, yaw=-1.5708 rad. The yaw correction compensates for the LiDAR&#039;s physical mounting angle.&lt;br /&gt;
* In simulation, &#039;&#039;laser_frame -&amp;gt; lidar_link&#039;&#039; is published instead, because Gazebo already provides &#039;&#039;laser_frame&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
The launch file uses the &#039;&#039;in_sim&#039;&#039; launch configuration to select the real or simulated LiDAR transform.&lt;br /&gt;
&lt;br /&gt;
=== launch/odometry.launch.py ===&lt;br /&gt;
&lt;br /&gt;
Starts the odometry estimation path. On the real robot it runs both the Madgwick IMU filter and the robot_localization EKF. In simulation it only runs the EKF with the simulation-specific configuration.&lt;br /&gt;
&lt;br /&gt;
Launch argument:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;use_sim_time&#039;&#039; - selects real or simulated odometry configuration. Default: &#039;&#039;False&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
Real robot nodes:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;imu_filter_madgwick_node&#039;&#039; named &#039;&#039;imu_filter&#039;&#039; in namespace &#039;&#039;loc&#039;&#039;.&lt;br /&gt;
* &#039;&#039;robot_localization/ekf_node&#039;&#039; named &#039;&#039;ekf&#039;&#039; in namespace &#039;&#039;loc&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
Real robot remappings:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;imu/mag -&amp;gt; /fejemis/mag&#039;&#039;&lt;br /&gt;
* &#039;&#039;imu/data_raw -&amp;gt; /fejemis/imu&#039;&#039;&lt;br /&gt;
* &#039;&#039;imu/data -&amp;gt; imu&#039;&#039;, which resolves to &#039;&#039;/loc/imu&#039;&#039;.&lt;br /&gt;
* &#039;&#039;/loc/odom_in -&amp;gt; /fejemis/odom&#039;&#039;&lt;br /&gt;
* &#039;&#039;odometry/filtered -&amp;gt; odom&#039;&#039;, which resolves to &#039;&#039;/loc/odom&#039;&#039;.&lt;br /&gt;
* diagnostics are remapped to &#039;&#039;/loc/ekf/_diagnostics&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
The EKF configuration in &#039;&#039;config/odometry.yaml&#039;&#039; runs at 30 Hz, uses two-dimensional mode, publishes TF, and fuses:&lt;br /&gt;
&lt;br /&gt;
* Wheel odometry from &#039;&#039;/fejemis/odom&#039;&#039;.&lt;br /&gt;
* RTAB-Map ICP odometry from &#039;&#039;/loc/rtabmap_odom&#039;&#039;.&lt;br /&gt;
* Filtered IMU input is present as &#039;&#039;/loc/imu&#039;&#039;, although the current real configuration disables the IMU state variables in the EKF and relies more heavily on odometry sources for motion estimation.&lt;br /&gt;
&lt;br /&gt;
Simulation mode uses &#039;&#039;config/odometry_sim.yaml&#039;&#039; and avoids the Madgwick IMU orientation because the simulated IMU orientation can conflict with Gazebo odometry frame conventions.&lt;br /&gt;
&lt;br /&gt;
[[File:fejemis_maploc_ekf_block_diagram.png| 800px]]&lt;br /&gt;
&lt;br /&gt;
EKF input/output diagram: &#039;&#039;/fejemis/odom&#039;&#039;, &#039;&#039;/loc/rtabmap_odom&#039;&#039;, &#039;&#039;/loc/imu&#039;&#039; into &#039;&#039;/loc/ekf&#039;&#039;, output &#039;&#039;/loc/odom&#039;&#039; and TF.&lt;br /&gt;
&lt;br /&gt;
=== launch/realsense.launch.py ===&lt;br /&gt;
&lt;br /&gt;
Includes the official &#039;&#039;realsense2_camera&#039;&#039; launch file with the package&#039;s &#039;&#039;config/realsense.yaml&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
Launch arguments:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;camera_namespace&#039;&#039; - namespace for the camera. Default: empty string.&lt;br /&gt;
* &#039;&#039;use_sim_time&#039;&#039; - declared for consistency, although the included launch is mainly configured through the RealSense launch arguments.&lt;br /&gt;
&lt;br /&gt;
Important RealSense settings:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;publish_tf=true&#039;&#039;&lt;br /&gt;
* &#039;&#039;enable_sync=true&#039;&#039;&lt;br /&gt;
* &#039;&#039;unite_imu_method=2&#039;&#039;&lt;br /&gt;
* &#039;&#039;config_file=fejemis_maploc/config/realsense.yaml&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The camera publishes the RGB, depth, camera info, IMU, and TF data used by RTAB-Map and by the depth-to-scan obstacle conversion.&lt;br /&gt;
&lt;br /&gt;
=== launch/rtabmap_slam.launch.py ===&lt;br /&gt;
&lt;br /&gt;
Starts the RTAB-Map mapping/localisation pipeline and includes &#039;&#039;tf.launch.py&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
Launch arguments:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;use_sim_time&#039;&#039; - selects real or simulation config. Default: &#039;&#039;false&#039;&#039;.&lt;br /&gt;
* &#039;&#039;localization&#039;&#039; - if &#039;&#039;true&#039;&#039;, RTAB-Map loads an existing database and does not increment the map. If &#039;&#039;false&#039;&#039;, it starts in mapping mode and deletes the database on start. Default: &#039;&#039;false&#039;&#039;.&lt;br /&gt;
* &#039;&#039;namespace&#039;&#039; - namespace for RTAB-Map. Default: &#039;&#039;loc&#039;&#039;.&lt;br /&gt;
* &#039;&#039;map_location&#039;&#039; - selects the database path. &#039;&#039;lab&#039;&#039; maps to &#039;&#039;~/.ros/rtabmap.db&#039;&#039; and &#039;&#039;asta&#039;&#039; maps to &#039;&#039;~/.ros/rtabmap_asta.db&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
Sensor remappings:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;rgb/image -&amp;gt; /camera/color/image_raw&#039;&#039;&lt;br /&gt;
* &#039;&#039;rgb/camera_info -&amp;gt; /camera/color/camera_info&#039;&#039;&lt;br /&gt;
* &#039;&#039;depth/image -&amp;gt; /camera/depth/image_rect_raw&#039;&#039;&lt;br /&gt;
* &#039;&#039;depth/camera_info -&amp;gt; /camera/depth/camera_info&#039;&#039;&lt;br /&gt;
* &#039;&#039;imu -&amp;gt; /loc/imu&#039;&#039;&lt;br /&gt;
* &#039;&#039;scan -&amp;gt; /fejemis/scan&#039;&#039;&lt;br /&gt;
* &#039;&#039;initialpose -&amp;gt; /initialpose&#039;&#039;&lt;br /&gt;
* &#039;&#039;grid_prob_map -&amp;gt; /map&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Nodes:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;depthimage_to_laserscan_node&#039;&#039; converts the depth image into &#039;&#039;/camera_scan&#039;&#039;.&lt;br /&gt;
* &#039;&#039;rtabmap_sync/rgbd_sync&#039;&#039; synchronises RGB-D data on the real robot.&lt;br /&gt;
* &#039;&#039;rtabmap_odom/icp_odometry&#039;&#039; publishes ICP odometry to &#039;&#039;/loc/rtabmap_odom&#039;&#039; on the real robot.&lt;br /&gt;
* &#039;&#039;rtabmap_slam/rtabmap&#039;&#039; runs either SLAM mode or localisation mode.&lt;br /&gt;
&lt;br /&gt;
In SLAM mode, &#039;&#039;delete_db_on_start=True&#039;&#039; and RTAB-Map is launched with &#039;&#039;-d&#039;&#039;, so the selected database is overwritten. In localisation mode, &#039;&#039;Mem/IncrementalMemory=False&#039;&#039; and &#039;&#039;Mem/InitWMWithAllNodes=True&#039;&#039;, so the existing map is loaded for localisation.&lt;br /&gt;
&lt;br /&gt;
=== launch/map.launch.py ===&lt;br /&gt;
&lt;br /&gt;
Starts a component-based Nav2 map server in namespace &#039;&#039;loc&#039;&#039;, using &#039;&#039;config/nav2_servers.yaml&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
Launch arguments:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;use_sim_time&#039;&#039; - forwarded to the map server.&lt;br /&gt;
* &#039;&#039;map_make_container&#039;&#039;, &#039;&#039;map_container&#039;&#039;, and &#039;&#039;map_container_ns&#039;&#039; - declared as container-related options. The current launch creates a container named &#039;&#039;_map&#039;&#039; with prefix/namespace &#039;&#039;map&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
The map server is configured with:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;topic_name=/loc/map&#039;&#039;&lt;br /&gt;
* &#039;&#039;frame_id=map&#039;&#039;&lt;br /&gt;
* &#039;&#039;yaml_filename=&amp;quot;&amp;quot;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
In the current system, the map is primarily produced by RTAB-Map, while the Nav2 map server exposes map-server functionality for the navigation stack.&lt;br /&gt;
&lt;br /&gt;
=== launch/planning.launch.py ===&lt;br /&gt;
&lt;br /&gt;
Starts the Nav2 planning and navigation servers, the Fejemis velocity relay, the brush service, and optionally the OpenNav coverage server.&lt;br /&gt;
&lt;br /&gt;
Launch arguments:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;namespace&#039;&#039; - namespace for Nav2 nodes. Default: &#039;&#039;fejemis&#039;&#039;.&lt;br /&gt;
* &#039;&#039;use_sim_time&#039;&#039; - forwarded to nodes. Default: &#039;&#039;False&#039;&#039;.&lt;br /&gt;
* &#039;&#039;enable_coverage&#039;&#039; - enables &#039;&#039;opennav_coverage&#039;&#039;. The default is detected from whether the coverage packages are installed.&lt;br /&gt;
&lt;br /&gt;
Nodes:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;nav2_controller/controller_server&#039;&#039; named &#039;&#039;controller&#039;&#039;.&lt;br /&gt;
* &#039;&#039;fejemis_maploc/nav2_cmd_vel_deg_relay.py&#039;&#039;.&lt;br /&gt;
* &#039;&#039;nav2_behaviors/behavior_server&#039;&#039; named &#039;&#039;behavior&#039;&#039;.&lt;br /&gt;
* &#039;&#039;nav2_planner/planner_server&#039;&#039; named &#039;&#039;planner_server&#039;&#039;.&lt;br /&gt;
* &#039;&#039;nav2_bt_navigator/bt_navigator&#039;&#039; named &#039;&#039;bt_navigator&#039;&#039;.&lt;br /&gt;
* &#039;&#039;nav2_map_server/map_server&#039;&#039;.&lt;br /&gt;
* &#039;&#039;fejemis_maploc/brush_control_service.py&#039;&#039; in namespace &#039;&#039;control/brush&#039;&#039;, named &#039;&#039;controller&#039;&#039;.&lt;br /&gt;
* &#039;&#039;opennav_coverage/opennav_coverage&#039;&#039; named &#039;&#039;coverage_server&#039;&#039;, when enabled.&lt;br /&gt;
&lt;br /&gt;
Important remappings:&lt;br /&gt;
&lt;br /&gt;
* Nav2 odometry is remapped from &#039;&#039;/behavior/odom&#039;&#039; to &#039;&#039;/loc/odom&#039;&#039;.&lt;br /&gt;
* Nav2 map input is remapped from &#039;&#039;/behavior/map&#039;&#039; to &#039;&#039;/loc/map&#039;&#039;.&lt;br /&gt;
* Nav2 controller output &#039;&#039;cmd_vel&#039;&#039; is remapped to &#039;&#039;/control/nav2/cmd_vel&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
The velocity relay then converts angular velocity units and republishes to &#039;&#039;/control/joy/cmd_vel&#039;&#039; for the existing Fejemis control path.&lt;br /&gt;
&lt;br /&gt;
The Nav2 configuration in &#039;&#039;config/nav2_servers.yaml&#039;&#039; uses:&lt;br /&gt;
&lt;br /&gt;
* Regulated Pure Pursuit as the path follower.&lt;br /&gt;
* Navfn as the global planner.&lt;br /&gt;
* Local costmap sources from LiDAR, camera scan, net scan, and cable scan.&lt;br /&gt;
* Global costmap with static map, LiDAR obstacle layer, and inflation layer.&lt;br /&gt;
* The OpenNav coverage navigator when coverage support is enabled.&lt;br /&gt;
&lt;br /&gt;
=== launch/lifecycle.launch.py ===&lt;br /&gt;
&lt;br /&gt;
Starts Nav2 lifecycle managers.&lt;br /&gt;
&lt;br /&gt;
Launch arguments:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;in_sim&#039;&#039; - forwarded as &#039;&#039;use_sim_time&#039;&#039;.&lt;br /&gt;
* &#039;&#039;enable_loc_lfc&#039;&#039; - controls whether the localisation lifecycle manager is started. Default: &#039;&#039;false&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
Lifecycle managers:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;/fejemis/beh_lfc&#039;&#039; - behaviour/navigation lifecycle manager.&lt;br /&gt;
* &#039;&#039;/loc/loc_lfc&#039;&#039; - localisation lifecycle manager, only when &#039;&#039;enable_loc_lfc=true&#039;&#039;.&lt;br /&gt;
* &#039;&#039;/fejemis/hard_lfc&#039;&#039; - hardware lifecycle manager, disabled in simulation.&lt;br /&gt;
&lt;br /&gt;
Diagnostics are remapped to &#039;&#039;/diag/beh_lfc&#039;&#039;, &#039;&#039;/diag/loc_lfc&#039;&#039;, and &#039;&#039;/diag/hard_lfc&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
=== launch/lifecycle_managers_only.launch.py ===&lt;br /&gt;
&lt;br /&gt;
Starts the navigation-side launch files without the heavy sensor components. This is useful when another machine is responsible for LiDAR, RealSense, or RTAB-Map.&lt;br /&gt;
&lt;br /&gt;
Launch arguments:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;in_sim&#039;&#039; - enables simulation time.&lt;br /&gt;
* &#039;&#039;localization&#039;&#039; - when true, starts the planning stack on the main Pi. Default: &#039;&#039;false&#039;&#039;.&lt;br /&gt;
* &#039;&#039;enable_coverage&#039;&#039; - enables coverage navigation. Default: &#039;&#039;true&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
Included launch files:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;planning.launch.py&#039;&#039; is included only when &#039;&#039;in_sim=true&#039;&#039; or &#039;&#039;localization=true&#039;&#039;.&lt;br /&gt;
* &#039;&#039;lifecycle.launch.py&#039;&#039; is included immediately.&lt;br /&gt;
* &#039;&#039;behavior.launch.py&#039;&#039; is included after a 30 second delay, giving Nav2 servers time to become active before the BT engine connects to action servers.&lt;br /&gt;
&lt;br /&gt;
=== launch/behavior.launch.py ===&lt;br /&gt;
&lt;br /&gt;
Starts the Fejemis behaviour tree engine from &#039;&#039;raubase_core&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
Launch argument:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;use_sim_time&#039;&#039; - forwarded to the BT engine.&lt;br /&gt;
&lt;br /&gt;
The launch file selects the behaviour tree XML based on the ROS distribution:&lt;br /&gt;
&lt;br /&gt;
* Older BehaviourTree.CPP format: &#039;&#039;resources/behavior/main.xml&#039;&#039;.&lt;br /&gt;
* BehaviourTree.CPP v4 format: &#039;&#039;resources/behavior/main4.xml&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
The node launched is:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;raubase_core/bt_engine&#039;&#039;, configured by &#039;&#039;config/bt_engine.yaml&#039;&#039; in namespace &#039;&#039;fejemis&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
The BT plugin configuration loads:&lt;br /&gt;
&lt;br /&gt;
* raubase logging, mission, blackboard, coverage, and mixer plugins.&lt;br /&gt;
* Nav2 action plugins for single trigger, follow path, drive on heading, spin, and navigate to pose.&lt;br /&gt;
* OpenNav coverage path computation plugin.&lt;br /&gt;
* The package&#039;s custom &#039;&#039;fejemis_call_trigger_service_bt_node&#039;&#039; plugin.&lt;br /&gt;
&lt;br /&gt;
=== launch/robot.launch.py inclusion ===&lt;br /&gt;
&lt;br /&gt;
The top-level &#039;&#039;fejemis&#039;&#039; package includes &#039;&#039;fejemis_maploc&#039;&#039; from &#039;&#039;fejemis/launch/robot.launch.py&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
Typical distribution:&lt;br /&gt;
&lt;br /&gt;
* Main Pi: bridge control, odometry, lifecycle managers, behaviour tree, and planning when localisation is enabled.&lt;br /&gt;
* Auxiliary Pi: LiDAR, RTAB-Map, RealSense, and optional vision.&lt;br /&gt;
&lt;br /&gt;
The top-level launch waits 3 seconds after bridge startup before launching the rest of the stack.&lt;br /&gt;
&lt;br /&gt;
== Script Nodes ==&lt;br /&gt;
&lt;br /&gt;
=== nav2_cmd_vel_deg_relay.py ===&lt;br /&gt;
&lt;br /&gt;
Purpose: adapts Nav2 velocity commands to the Fejemis control convention.&lt;br /&gt;
&lt;br /&gt;
Subscriptions:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;/control/nav2/cmd_vel&#039;&#039; - &#039;&#039;geometry_msgs/Twist&#039;&#039;, with angular velocity in rad/s.&lt;br /&gt;
&lt;br /&gt;
Publications:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;/control/joy/cmd_vel&#039;&#039; - &#039;&#039;geometry_msgs/Twist&#039;&#039;, with angular velocity converted to deg/s.&lt;br /&gt;
&lt;br /&gt;
The linear fields are copied unchanged. The angular x, y, and z fields are multiplied by 180/pi. This is needed because Nav2 uses the normal ROS convention of rad/s, while the existing Fejemis bridge control path expects angular velocity in deg/s.&lt;br /&gt;
&lt;br /&gt;
=== brush_control_service.py ===&lt;br /&gt;
&lt;br /&gt;
Purpose: exposes simple ROS services for turning the brush on and off.&lt;br /&gt;
&lt;br /&gt;
Parameters:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;manage_topic&#039;&#039; - topic for &#039;&#039;fejemis_bridge/msg/SteerManage&#039;&#039;. Default: &#039;&#039;/fejemis/front/manage&#039;&#039;.&lt;br /&gt;
* &#039;&#039;qos_depth&#039;&#039; - publisher QoS depth. Default: &#039;&#039;10&#039;&#039;.&lt;br /&gt;
* &#039;&#039;initial_brush_on&#039;&#039; - initial internal state. Default: &#039;&#039;False&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
Publications:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;manage_topic&#039;&#039; - publishes &#039;&#039;SteerManage.BRUSH_ON&#039;&#039; or &#039;&#039;SteerManage.BRUSH_OFF&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
Services:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;~/set&#039;&#039; - &#039;&#039;std_srvs/SetBool&#039;&#039;, true means brush on and false means brush off.&lt;br /&gt;
* &#039;&#039;~/on&#039;&#039; - &#039;&#039;std_srvs/Trigger&#039;&#039;, turns the brush on.&lt;br /&gt;
* &#039;&#039;~/off&#039;&#039; - &#039;&#039;std_srvs/Trigger&#039;&#039;, turns the brush off.&lt;br /&gt;
&lt;br /&gt;
In &#039;&#039;planning.launch.py&#039;&#039; this node runs as &#039;&#039;/control/brush/controller&#039;&#039;, which creates the services used by the behaviour tree:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;/control/brush/controller/on&#039;&#039;&lt;br /&gt;
* &#039;&#039;/control/brush/controller/off&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
=== rviz_goal_relay.py ===&lt;br /&gt;
&lt;br /&gt;
Purpose: lets an operator send a Nav2 goal from RViz.&lt;br /&gt;
&lt;br /&gt;
Subscriptions:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;/goal_pose&#039;&#039; - RViz 2D Nav Goal as &#039;&#039;geometry_msgs/PoseStamped&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
Action clients:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;/fejemis/navigate_to_pose&#039;&#039; - &#039;&#039;nav2_msgs/action/NavigateToPose&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
When a goal is clicked in RViz, the node checks that the action server is ready, forwards the goal, and logs whether it was accepted and whether it succeeded.&lt;br /&gt;
&lt;br /&gt;
=== mission_trigger.py ===&lt;br /&gt;
&lt;br /&gt;
Purpose: starts a cleaning mission by publishing blackboard values and then setting the active mission.&lt;br /&gt;
&lt;br /&gt;
Parameters:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;mission&#039;&#039; - mission string to publish. Default: &#039;&#039;cleaning/waypoint&#039;&#039;.&lt;br /&gt;
* &#039;&#039;dock_pose&#039;&#039; - dock pose as &#039;&#039;[x, y, yaw]&#039;&#039;. Default: &#039;&#039;[2.62, 3.9, 0.0]&#039;&#039;.&lt;br /&gt;
* &#039;&#039;waypoint_pose&#039;&#039; - waypoint pose as &#039;&#039;[x, y, yaw]&#039;&#039;. Default: &#039;&#039;[2.10491, 0.497438, 3.11127]&#039;&#039;.&lt;br /&gt;
* &#039;&#039;clean_poly&#039;&#039; - polygon string used for cleaning area blackboard values.&lt;br /&gt;
* &#039;&#039;startup_delay&#039;&#039; - delay before publishing. Default: &#039;&#039;2.0&#039;&#039; seconds.&lt;br /&gt;
&lt;br /&gt;
Publications:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;/blackboard/dock_x&#039;&#039;, &#039;&#039;/blackboard/dock_y&#039;&#039;, &#039;&#039;/blackboard/dock_yaw&#039;&#039;&lt;br /&gt;
* &#039;&#039;/blackboard/waypoint_x&#039;&#039;, &#039;&#039;/blackboard/waypoint_y&#039;&#039;, &#039;&#039;/blackboard/waypoint_yaw&#039;&#039;&lt;br /&gt;
* &#039;&#039;/blackboard/field_polygon&#039;&#039;&lt;br /&gt;
* &#039;&#039;/blackboard/clean_poly&#039;&#039;&lt;br /&gt;
* &#039;&#039;/blackboard/start_pose&#039;&#039;&lt;br /&gt;
* &#039;&#039;/blackboard/dock_pose&#039;&#039;&lt;br /&gt;
* &#039;&#039;/blackboard/waypoint_pose&#039;&#039;&lt;br /&gt;
* &#039;&#039;/behavior/set_mission&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The blackboard publishers use transient-local QoS so late subscribers can still receive the latest values. Pose strings are encoded as:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
stamp;frame;x;y;z;qx;qy;qz;qw&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The node also subscribes to &#039;&#039;/behavior/set_mission&#039;&#039;. When it sees &#039;&#039;auto/idle&#039;&#039; after the mission has run, it logs completion and shuts down.&lt;br /&gt;
&lt;br /&gt;
=== complete_coverage_planner.py ===&lt;br /&gt;
&lt;br /&gt;
Purpose: interactive coverage test node for RViz.&lt;br /&gt;
&lt;br /&gt;
Parameters:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;rviz_click_topic&#039;&#039; - clicked point topic. Default: &#039;&#039;/clicked_point&#039;&#039;.&lt;br /&gt;
* &#039;&#039;coverage_frame&#039;&#039; - frame for the coverage polygon. Default: &#039;&#039;map&#039;&#039;.&lt;br /&gt;
* &#039;&#039;min_polygon_points&#039;&#039; - number of clicked points before sending a goal. Default: &#039;&#039;4&#039;&#039;.&lt;br /&gt;
* &#039;&#039;auto_close_polygon&#039;&#039; - whether to append the first point as the final point. Default: &#039;&#039;True&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
Subscriptions:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;/clicked_point&#039;&#039; by default - RViz point clicks.&lt;br /&gt;
&lt;br /&gt;
Action clients:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;fejemis/navigate_complete_coverage&#039;&#039; - &#039;&#039;opennav_coverage_msgs/action/NavigateCompleteCoverage&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
The node waits for &#039;&#039;fejemis/bt_navigator&#039;&#039; to become active, collects clicked points in RViz, converts them to a &#039;&#039;geometry_msgs/Polygon&#039;&#039;, and sends a coverage navigation action once enough points have been collected.&lt;br /&gt;
&lt;br /&gt;
=== fixed_complete_coverage_planner.py ===&lt;br /&gt;
&lt;br /&gt;
Purpose: fixed-polygon coverage test node.&lt;br /&gt;
&lt;br /&gt;
Parameters:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;coverage_frame&#039;&#039; - frame for the coverage polygon. Default: &#039;&#039;map&#039;&#039;.&lt;br /&gt;
* &#039;&#039;min_polygon_points&#039;&#039; - retained from the interactive tester. Default: &#039;&#039;4&#039;&#039;.&lt;br /&gt;
* &#039;&#039;auto_close_polygon&#039;&#039; - whether to append the first point as the final point. Default: &#039;&#039;True&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
Action clients:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;fejemis/navigate_complete_coverage&#039;&#039; - &#039;&#039;opennav_coverage_msgs/action/NavigateCompleteCoverage&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
The node waits for &#039;&#039;fejemis/bt_navigator&#039;&#039; to become active, then sends a fixed four-point polygon:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
(-5.35,  2.45)&lt;br /&gt;
( 2.83,  3.18)&lt;br /&gt;
( 3.22, -5.08)&lt;br /&gt;
(-4.76, -5.97)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This is useful for testing coverage planning without clicking points manually in RViz.&lt;br /&gt;
&lt;br /&gt;
== C++ Behaviour Tree Plugin ==&lt;br /&gt;
&lt;br /&gt;
=== fejemis_call_trigger_service_bt_node ===&lt;br /&gt;
&lt;br /&gt;
The C++ plugin &#039;&#039;src/bt_plugins/call_trigger_service_bt_node.cpp&#039;&#039; registers the behaviour tree node &#039;&#039;CallTriggerService&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
Inputs:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;service_name&#039;&#039; - service to call.&lt;br /&gt;
* &#039;&#039;wait_for_service_ms&#039;&#039; - timeout while waiting for service availability. Default: &#039;&#039;1000&#039;&#039;.&lt;br /&gt;
* &#039;&#039;call_timeout_ms&#039;&#039; - timeout for the service call. Default: &#039;&#039;2000&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
The node creates a &#039;&#039;std_srvs/Trigger&#039;&#039; client, waits for the target service, calls it, and returns:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;SUCCESS&#039;&#039; when the service exists, responds before timeout, and returns &#039;&#039;success=true&#039;&#039;.&lt;br /&gt;
* &#039;&#039;FAILURE&#039;&#039; when the service is unavailable, the call times out, or the response reports failure.&lt;br /&gt;
&lt;br /&gt;
The cleaning subtree uses it to call:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;/control/brush/controller/on&#039;&#039;&lt;br /&gt;
* &#039;&#039;/control/brush/controller/off&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
== Behaviour Tree ==&lt;br /&gt;
&lt;br /&gt;
The active behaviour tree on newer ROS distributions is &#039;&#039;resources/behavior/main4.xml&#039;&#039;. It is loaded by &#039;&#039;behavior.launch.py&#039;&#039; through the &#039;&#039;raubase_core/bt_engine&#039;&#039; node.&lt;br /&gt;
&lt;br /&gt;
=== Main Loop ===&lt;br /&gt;
&lt;br /&gt;
The root tree is &#039;&#039;main&#039;&#039;:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
main&lt;br /&gt;
`-- Sequence&lt;br /&gt;
    |-- SingleTrigger&lt;br /&gt;
    |   `-- t_init&lt;br /&gt;
    `-- KeepRunningUntilFailure&lt;br /&gt;
        `-- ReactiveSequence&lt;br /&gt;
            |-- ForceSuccess&lt;br /&gt;
            |   `-- t_passive&lt;br /&gt;
            `-- t_runtime&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The structure means:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;t_init&#039;&#039; runs once at startup.&lt;br /&gt;
* &#039;&#039;t_passive&#039;&#039; runs repeatedly and updates blackboard/mission state.&lt;br /&gt;
* &#039;&#039;t_runtime&#039;&#039; selects the active behaviour based on the current mission.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Visualization placeholder:&#039;&#039;&#039; exported Groot image of &#039;&#039;main4.xml&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
=== t_init ===&lt;br /&gt;
&lt;br /&gt;
Initialises the robot behaviour state:&lt;br /&gt;
&lt;br /&gt;
* Sets mission to &#039;&#039;auto/idle&#039;&#039;.&lt;br /&gt;
* Sets blackboard key &#039;&#039;initialized=1&#039;&#039;.&lt;br /&gt;
* Sets calibration square distance &#039;&#039;calib_sq_dist=7&#039;&#039;.&lt;br /&gt;
* Opens the mixer port using &#039;&#039;OpenMixerPort&#039;&#039; with priority 4 and topic &#039;&#039;/behavior&#039;&#039;.&lt;br /&gt;
* Logs that the robot is initialized.&lt;br /&gt;
&lt;br /&gt;
=== t_passive ===&lt;br /&gt;
&lt;br /&gt;
Runs as the passive safety/update loop:&lt;br /&gt;
&lt;br /&gt;
* Logs &#039;&#039;Safety loop&#039;&#039; at debug level.&lt;br /&gt;
* Refreshes &#039;&#039;start_pose&#039;&#039;, &#039;&#039;waypoint_pose&#039;&#039;, &#039;&#039;clean_poly&#039;&#039;, and &#039;&#039;field_polygon&#039;&#039; from &#039;&#039;/blackboard/...&#039;&#039; topics.&lt;br /&gt;
* Refreshes the current mission from &#039;&#039;/behavior/set_mission&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
=== t_runtime ===&lt;br /&gt;
&lt;br /&gt;
Selects behaviour using a reactive fallback:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
t_runtime&lt;br /&gt;
|-- t_idle_runtime&lt;br /&gt;
|-- t_calib&lt;br /&gt;
|-- t_mapping&lt;br /&gt;
|-- t_cleaning&lt;br /&gt;
`-- unknown mission handler -&amp;gt; set idle&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The first matching mission branch runs. If no known branch matches, the tree logs an error and returns to idle.&lt;br /&gt;
&lt;br /&gt;
=== Idle ===&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;t_idle_runtime&#039;&#039; succeeds when the current mission is:&lt;br /&gt;
&lt;br /&gt;
* mission: &#039;&#039;auto&#039;&#039;&lt;br /&gt;
* mission_sub: &#039;&#039;idle&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
This keeps the robot in a waiting state. Sensors, telemetry, and subscriptions remain active, but no movement commands are issued. The robot leaves this state when a new mission is received through &#039;&#039;/behavior/set_mission&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
=== Calibration ===&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;t_calib&#039;&#039; chooses between line calibration and square calibration.&lt;br /&gt;
&lt;br /&gt;
Line calibration, &#039;&#039;t_calib_line&#039;&#039;:&lt;br /&gt;
&lt;br /&gt;
* Requires mission &#039;&#039;calib/line&#039;&#039;.&lt;br /&gt;
* Drives forward using &#039;&#039;DriveOnHeading&#039;&#039; &lt;br /&gt;
* &#039;&#039;calib_sq_dist&#039;&#039; is set to 0.05 m/s during initialization.&lt;br /&gt;
* Returns to idle.&lt;br /&gt;
&lt;br /&gt;
Square calibration, &#039;&#039;t_calib_square&#039;&#039;:&lt;br /&gt;
&lt;br /&gt;
* Requires mission &#039;&#039;calib/*&#039;&#039;.&lt;br /&gt;
* Runs &#039;&#039;f_calib_side&#039;&#039; four times.&lt;br /&gt;
* Each side drives forward and spins 1.57 rad.&lt;br /&gt;
* Returns to idle.&lt;br /&gt;
Each &#039;&#039;f_calib_side&#039;&#039; contains:&lt;br /&gt;
&lt;br /&gt;
*DriveOnHeading&lt;br /&gt;
*Spin&lt;br /&gt;
*A quarter-turn style calibration step&lt;br /&gt;
Calibration succeeds when each action in the sequence completes successfully.&lt;br /&gt;
&lt;br /&gt;
=== Mapping ===&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;t_mapping&#039;&#039; currently checks for &#039;&#039;mission mapping/*&#039;&#039; and then returns to idle.&lt;br /&gt;
&lt;br /&gt;
*Requires mission mapping/*&lt;br /&gt;
&lt;br /&gt;
*Currently acts as a placeholder&lt;br /&gt;
&lt;br /&gt;
*Does not yet launch the SLAM or exploration pipeline&lt;br /&gt;
&lt;br /&gt;
*Returns to idle immediately&lt;br /&gt;
&lt;br /&gt;
This branch should later be expanded to perform autonomous mapping, localization checks, and map saving.&lt;br /&gt;
&lt;br /&gt;
=== Cleaning ===&lt;br /&gt;
&lt;br /&gt;
The current cleaning branch is &#039;&#039;t_cleaning&#039;&#039;:&lt;br /&gt;
&lt;br /&gt;
* Requires mission &#039;&#039;cleaning/waypoint&#039;&#039; with mission_sub &#039;&#039;idle&#039;&#039;.&lt;br /&gt;
* Navigates to &#039;&#039;{waypoint_pose}&#039;&#039; using &#039;&#039;NavigateToPose&#039;&#039;.&lt;br /&gt;
* Sets current mission to &#039;&#039;cleaning/area&#039;&#039;.&lt;br /&gt;
* Returns to idle.&lt;br /&gt;
&lt;br /&gt;
Supporting cleaning subtrees are already present:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;f_get_area_to_clean&#039;&#039; sets a polygon and calls &#039;&#039;ComputeCoveragePath&#039;&#039;.&lt;br /&gt;
* &#039;&#039;f_clean_area&#039;&#039; turns the brush on, follows &#039;&#039;{cover_nav_path}&#039;&#039;, and turns the brush off.&lt;br /&gt;
* &#039;&#039;f_go_to_path_start&#039;&#039; is a placeholder that logs navigation to the path start.&lt;br /&gt;
&lt;br /&gt;
These subtrees show the intended full cleaning sequence, although the active &#039;&#039;t_cleaning&#039;&#039; branch currently only navigates to a waypoint and changes mission state.&lt;br /&gt;
[[File:BT-cleaning.jpeg| 800px]]&lt;br /&gt;
&lt;br /&gt;
Cleaning Node Behaviour Sequence: mission trigger -&amp;gt; waypoint navigation -&amp;gt; coverage path computation -&amp;gt; brush on -&amp;gt; follow coverage path -&amp;gt; brush off -&amp;gt; idle.&lt;br /&gt;
&lt;br /&gt;
== Configuration Files ==&lt;br /&gt;
&lt;br /&gt;
=== config/odometry.yaml and config/odometry_sim.yaml ===&lt;br /&gt;
&lt;br /&gt;
Configure the EKF and IMU filter. The real configuration fuses wheel odometry and RTAB-Map ICP odometry in 2D mode. The simulation configuration is separated to avoid conflicting simulated IMU orientation.&lt;br /&gt;
&lt;br /&gt;
=== config/rtabmap.yaml and config/rtabmap_sim.yaml ===&lt;br /&gt;
&lt;br /&gt;
Configure RTAB-Map for real robot and simulation use. &#039;&#039;rtabmap_slam.launch.py&#039;&#039; selects between them using &#039;&#039;use_sim_time&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
=== config/nav2_servers.yaml and config/nav2_sim.yaml ===&lt;br /&gt;
&lt;br /&gt;
Configure Nav2 controller, planner, behaviour server, costmaps, BT navigator, coverage server, and map server. The main real-robot launch uses &#039;&#039;nav2_servers.yaml&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
=== config/bt_engine.yaml ===&lt;br /&gt;
&lt;br /&gt;
Lists behaviour tree plugins loaded by &#039;&#039;raubase_core/bt_engine&#039;&#039;, including the Fejemis custom trigger-service plugin.&lt;br /&gt;
&lt;br /&gt;
=== config/lifecycle.yaml ===&lt;br /&gt;
&lt;br /&gt;
Configures lifecycle manager node lists and autostart behaviour for the navigation, localisation, and hardware lifecycle managers.&lt;br /&gt;
&lt;br /&gt;
=== config/realsense.yaml ===&lt;br /&gt;
&lt;br /&gt;
RealSense camera configuration loaded by &#039;&#039;realsense.launch.py&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
== Typical Usage ==&lt;br /&gt;
&lt;br /&gt;
Start the complete robot stack through the top-level launch:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ros2 launch fejemis robot.launch.py localization:=true map_location:=asta&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Run on both Raspberry Pis. The launch file decides which parts run on the main and auxiliary computers through the &#039;&#039;is_aux&#039;&#039; argument.&lt;br /&gt;
&lt;br /&gt;
Start a waypoint cleaning mission after the system is up:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ros2 run fejemis_maploc mission_trigger.py&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Send an RViz navigation goal:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ros2 run fejemis_maploc rviz_goal_relay.py&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Test coverage navigation with RViz clicked points:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ros2 run fejemis_maploc complete_coverage_planner.py&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Test coverage navigation with the fixed polygon:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ros2 run fejemis_maploc fixed_complete_coverage_planner.py&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Notes and Future Work ==&lt;br /&gt;
&lt;br /&gt;
* Clarify which Raspberry Pi should run each launch file in the final deployment.&lt;br /&gt;
* Expand the cleaning behaviour section once &#039;&#039;t_cleaning&#039;&#039; is connected to &#039;&#039;f_get_area_to_clean&#039;&#039; and &#039;&#039;f_clean_area&#039;&#039; in the active tree.&lt;br /&gt;
* Test and validate the complete cleaning behaviour structure in the final hardware setup, as it has not yet been fully evaluated due to current Raspberry Pi limitations.&lt;/div&gt;</summary>
		<author><name>S252724</name></author>
	</entry>
	<entry>
		<id>https://rsewiki.electro.dtu.dk/index.php?title=Fejemis_Maploc_Ros2&amp;diff=8982</id>
		<title>Fejemis Maploc Ros2</title>
		<link rel="alternate" type="text/html" href="https://rsewiki.electro.dtu.dk/index.php?title=Fejemis_Maploc_Ros2&amp;diff=8982"/>
		<updated>2026-05-28T11:41:20Z</updated>

		<summary type="html">&lt;p&gt;S252724: /* Cleaning */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Back to [[Fejemis ROS2 Software]]&lt;br /&gt;
&lt;br /&gt;
== Purpose ==&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fejemis_maploc&#039;&#039; is the mapping, localisation, navigation, and behaviour package for Fejemis. It connects the robot&#039;s sensor data to the navigation stack and exposes the high-level behaviour tree used to run calibration, mapping, waypoint navigation, and cleaning missions.&lt;br /&gt;
&lt;br /&gt;
The package sits between the low-level bridge and the high-level mission logic:&lt;br /&gt;
&lt;br /&gt;
* The bridge publishes wheel odometry, IMU data, and accepts velocity / brush commands.&lt;br /&gt;
* &#039;&#039;fejemis_maploc&#039;&#039; filters odometry, starts LiDAR and RealSense sensing, runs RTAB-Map, starts Nav2, and runs the Fejemis behaviour tree.&lt;br /&gt;
* The behaviour tree sends commands to Nav2, the coverage planner, the mixer, and the brush service.&lt;br /&gt;
[[File:maploc_general_block_diagram.png| 1600px]]&lt;br /&gt;
&lt;br /&gt;
Overall &#039;&#039;fejemis_maploc&#039;&#039; data flow from bridge, LiDAR, and RealSense into odometry, RTAB-Map, Nav2, coverage planning, behaviour tree, and robot commands.&lt;br /&gt;
&lt;br /&gt;
== Package Layout ==&lt;br /&gt;
&lt;br /&gt;
Important package folders:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;resources/launch&#039;&#039; - ROS 2 launch files for sensors, odometry, SLAM/localisation, planning, lifecycle managers, and behaviour trees.&lt;br /&gt;
* &#039;&#039;resources/config&#039;&#039; - YAML configuration for odometry, Nav2, RTAB-Map, RealSense, lifecycle managers, and BT plugins.&lt;br /&gt;
* &#039;&#039;resources/behavior&#039;&#039; - behaviour tree XML files. &#039;&#039;main.xml&#039;&#039; is the older BT.CPP format and &#039;&#039;main4.xml&#039;&#039; is the BT.CPP v4 version used on newer ROS distributions.&lt;br /&gt;
* &#039;&#039;scripts&#039;&#039; - Python helper nodes for mission triggering, RViz goal relays, brush services, velocity conversion, and coverage tests.&lt;br /&gt;
* &#039;&#039;src/bt_plugins&#039;&#039; - C++ behaviour tree plugin used by the cleaning behaviour.&lt;br /&gt;
&lt;br /&gt;
== Main Topics and Frames ==&lt;br /&gt;
&lt;br /&gt;
Common frame names:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;map&#039;&#039; - global map frame produced by RTAB-Map / Nav2.&lt;br /&gt;
* &#039;&#039;odom&#039;&#039; - local odometry frame.&lt;br /&gt;
* &#039;&#039;base_link&#039;&#039; - robot base frame.&lt;br /&gt;
* &#039;&#039;lidar_link&#039;&#039; - LiDAR frame.&lt;br /&gt;
* &#039;&#039;camera_camera_link&#039;&#039; and RealSense optical frames - camera frames used by the RealSense driver and RTAB-Map.&lt;br /&gt;
&lt;br /&gt;
Important topics and actions:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;/fejemis/odom&#039;&#039; - wheel odometry from the bridge.&lt;br /&gt;
* &#039;&#039;/fejemis/imu&#039;&#039; and &#039;&#039;/fejemis/mag&#039;&#039; - raw IMU and magnetometer inputs.&lt;br /&gt;
* &#039;&#039;/loc/imu&#039;&#039; - filtered IMU output from Madgwick.&lt;br /&gt;
* &#039;&#039;/loc/odom&#039;&#039; - fused odometry output from the EKF.&lt;br /&gt;
* &#039;&#039;/fejemis/scan&#039;&#039; - LiDAR scan.&lt;br /&gt;
* &#039;&#039;/camera/color/image_raw&#039;&#039;, &#039;&#039;/camera/depth/image_rect_raw&#039;&#039;, and camera info topics - RealSense RGB-D input.&lt;br /&gt;
* &#039;&#039;/camera_scan&#039;&#039; - depth-image-to-laserscan output used as an extra obstacle source.&lt;br /&gt;
* &#039;&#039;/loc/map&#039;&#039; - occupancy map used by Nav2.&lt;br /&gt;
* &#039;&#039;/control/nav2/cmd_vel&#039;&#039; - Nav2 velocity command before Fejemis-specific conversion.&lt;br /&gt;
* &#039;&#039;/control/joy/cmd_vel&#039;&#039; - velocity command sent into the existing Fejemis control path.&lt;br /&gt;
* &#039;&#039;/behavior/set_mission&#039;&#039; - mission command topic consumed by the behaviour tree.&lt;br /&gt;
* &#039;&#039;/blackboard/...&#039;&#039; - blackboard values mirrored into the behaviour tree.&lt;br /&gt;
* &#039;&#039;/fejemis/navigate_to_pose&#039;&#039; - Nav2 action used for waypoint goals.&lt;br /&gt;
* &#039;&#039;/fejemis/navigate_complete_coverage&#039;&#039; - OpenNav coverage action used for coverage missions.&lt;br /&gt;
* &#039;&#039;/control/brush/controller/on&#039;&#039; and &#039;&#039;/control/brush/controller/off&#039;&#039; - brush control trigger services.&lt;br /&gt;
&lt;br /&gt;
== Launch Files ==&lt;br /&gt;
&lt;br /&gt;
The package is designed so the top-level &#039;&#039;fejemis/launch/robot.launch.py&#039;&#039; can include only the parts needed on each Raspberry Pi. The main Pi typically handles bridge control and odometry, while the auxiliary Pi handles heavier sensing such as LiDAR, RealSense, RTAB-Map, and vision.&lt;br /&gt;
&lt;br /&gt;
=== launch/lidar.launch.py ===&lt;br /&gt;
&lt;br /&gt;
Starts the LD19 LiDAR driver from &#039;&#039;ldlidar_stl_ros2&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
Launch arguments:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;ld_make_container&#039;&#039; - whether the LiDAR launch should create a component container. Default: &#039;&#039;True&#039;&#039;.&lt;br /&gt;
* &#039;&#039;ld_container&#039;&#039; - component container name. Default: &#039;&#039;_laser&#039;&#039;.&lt;br /&gt;
* &#039;&#039;ld_container_ns&#039;&#039; - namespace for the component container. Default: &#039;&#039;loc&#039;&#039;.&lt;br /&gt;
* &#039;&#039;port_name&#039;&#039; - serial port for the LiDAR. Default: &#039;&#039;/dev/ttyUSB0&#039;&#039;.&lt;br /&gt;
* &#039;&#039;namespace&#039;&#039; - launch argument declared for compatibility. The included launch currently receives &#039;&#039;fejemis&#039;&#039; as namespace.&lt;br /&gt;
&lt;br /&gt;
Important configuration passed to the driver:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;laser_frame=lidar_link&#039;&#039;&lt;br /&gt;
* &#039;&#039;clockwise_dir=false&#039;&#039;&lt;br /&gt;
* &#039;&#039;range_min=0.2&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The resulting scan is used by RTAB-Map and by the Nav2 costmaps as the main 2D obstacle source.&lt;br /&gt;
&lt;br /&gt;
=== launch/tf.launch.py ===&lt;br /&gt;
&lt;br /&gt;
Publishes static transforms for the physical sensor layout.&lt;br /&gt;
&lt;br /&gt;
Published transforms:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;base_link -&amp;gt; camera_camera_link&#039;&#039; at approximately x=-0.24 m, y=0.0 m, z=0.44 m.&lt;br /&gt;
* On the real robot, &#039;&#039;base_link -&amp;gt; lidar_link&#039;&#039; at approximately x=-0.29 m, y=0.0 m, z=0.25 m, yaw=-1.5708 rad. The yaw correction compensates for the LiDAR&#039;s physical mounting angle.&lt;br /&gt;
* In simulation, &#039;&#039;laser_frame -&amp;gt; lidar_link&#039;&#039; is published instead, because Gazebo already provides &#039;&#039;laser_frame&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
The launch file uses the &#039;&#039;in_sim&#039;&#039; launch configuration to select the real or simulated LiDAR transform.&lt;br /&gt;
&lt;br /&gt;
=== launch/odometry.launch.py ===&lt;br /&gt;
&lt;br /&gt;
Starts the odometry estimation path. On the real robot it runs both the Madgwick IMU filter and the robot_localization EKF. In simulation it only runs the EKF with the simulation-specific configuration.&lt;br /&gt;
&lt;br /&gt;
Launch argument:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;use_sim_time&#039;&#039; - selects real or simulated odometry configuration. Default: &#039;&#039;False&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
Real robot nodes:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;imu_filter_madgwick_node&#039;&#039; named &#039;&#039;imu_filter&#039;&#039; in namespace &#039;&#039;loc&#039;&#039;.&lt;br /&gt;
* &#039;&#039;robot_localization/ekf_node&#039;&#039; named &#039;&#039;ekf&#039;&#039; in namespace &#039;&#039;loc&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
Real robot remappings:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;imu/mag -&amp;gt; /fejemis/mag&#039;&#039;&lt;br /&gt;
* &#039;&#039;imu/data_raw -&amp;gt; /fejemis/imu&#039;&#039;&lt;br /&gt;
* &#039;&#039;imu/data -&amp;gt; imu&#039;&#039;, which resolves to &#039;&#039;/loc/imu&#039;&#039;.&lt;br /&gt;
* &#039;&#039;/loc/odom_in -&amp;gt; /fejemis/odom&#039;&#039;&lt;br /&gt;
* &#039;&#039;odometry/filtered -&amp;gt; odom&#039;&#039;, which resolves to &#039;&#039;/loc/odom&#039;&#039;.&lt;br /&gt;
* diagnostics are remapped to &#039;&#039;/loc/ekf/_diagnostics&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
The EKF configuration in &#039;&#039;config/odometry.yaml&#039;&#039; runs at 30 Hz, uses two-dimensional mode, publishes TF, and fuses:&lt;br /&gt;
&lt;br /&gt;
* Wheel odometry from &#039;&#039;/fejemis/odom&#039;&#039;.&lt;br /&gt;
* RTAB-Map ICP odometry from &#039;&#039;/loc/rtabmap_odom&#039;&#039;.&lt;br /&gt;
* Filtered IMU input is present as &#039;&#039;/loc/imu&#039;&#039;, although the current real configuration disables the IMU state variables in the EKF and relies more heavily on odometry sources for motion estimation.&lt;br /&gt;
&lt;br /&gt;
Simulation mode uses &#039;&#039;config/odometry_sim.yaml&#039;&#039; and avoids the Madgwick IMU orientation because the simulated IMU orientation can conflict with Gazebo odometry frame conventions.&lt;br /&gt;
&lt;br /&gt;
[[File:fejemis_maploc_ekf_block_diagram.png| 800px]]&lt;br /&gt;
&lt;br /&gt;
EKF input/output diagram: &#039;&#039;/fejemis/odom&#039;&#039;, &#039;&#039;/loc/rtabmap_odom&#039;&#039;, &#039;&#039;/loc/imu&#039;&#039; into &#039;&#039;/loc/ekf&#039;&#039;, output &#039;&#039;/loc/odom&#039;&#039; and TF.&lt;br /&gt;
&lt;br /&gt;
=== launch/realsense.launch.py ===&lt;br /&gt;
&lt;br /&gt;
Includes the official &#039;&#039;realsense2_camera&#039;&#039; launch file with the package&#039;s &#039;&#039;config/realsense.yaml&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
Launch arguments:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;camera_namespace&#039;&#039; - namespace for the camera. Default: empty string.&lt;br /&gt;
* &#039;&#039;use_sim_time&#039;&#039; - declared for consistency, although the included launch is mainly configured through the RealSense launch arguments.&lt;br /&gt;
&lt;br /&gt;
Important RealSense settings:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;publish_tf=true&#039;&#039;&lt;br /&gt;
* &#039;&#039;enable_sync=true&#039;&#039;&lt;br /&gt;
* &#039;&#039;unite_imu_method=2&#039;&#039;&lt;br /&gt;
* &#039;&#039;config_file=fejemis_maploc/config/realsense.yaml&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The camera publishes the RGB, depth, camera info, IMU, and TF data used by RTAB-Map and by the depth-to-scan obstacle conversion.&lt;br /&gt;
&lt;br /&gt;
=== launch/rtabmap_slam.launch.py ===&lt;br /&gt;
&lt;br /&gt;
Starts the RTAB-Map mapping/localisation pipeline and includes &#039;&#039;tf.launch.py&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
Launch arguments:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;use_sim_time&#039;&#039; - selects real or simulation config. Default: &#039;&#039;false&#039;&#039;.&lt;br /&gt;
* &#039;&#039;localization&#039;&#039; - if &#039;&#039;true&#039;&#039;, RTAB-Map loads an existing database and does not increment the map. If &#039;&#039;false&#039;&#039;, it starts in mapping mode and deletes the database on start. Default: &#039;&#039;false&#039;&#039;.&lt;br /&gt;
* &#039;&#039;namespace&#039;&#039; - namespace for RTAB-Map. Default: &#039;&#039;loc&#039;&#039;.&lt;br /&gt;
* &#039;&#039;map_location&#039;&#039; - selects the database path. &#039;&#039;lab&#039;&#039; maps to &#039;&#039;~/.ros/rtabmap.db&#039;&#039; and &#039;&#039;asta&#039;&#039; maps to &#039;&#039;~/.ros/rtabmap_asta.db&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
Sensor remappings:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;rgb/image -&amp;gt; /camera/color/image_raw&#039;&#039;&lt;br /&gt;
* &#039;&#039;rgb/camera_info -&amp;gt; /camera/color/camera_info&#039;&#039;&lt;br /&gt;
* &#039;&#039;depth/image -&amp;gt; /camera/depth/image_rect_raw&#039;&#039;&lt;br /&gt;
* &#039;&#039;depth/camera_info -&amp;gt; /camera/depth/camera_info&#039;&#039;&lt;br /&gt;
* &#039;&#039;imu -&amp;gt; /loc/imu&#039;&#039;&lt;br /&gt;
* &#039;&#039;scan -&amp;gt; /fejemis/scan&#039;&#039;&lt;br /&gt;
* &#039;&#039;initialpose -&amp;gt; /initialpose&#039;&#039;&lt;br /&gt;
* &#039;&#039;grid_prob_map -&amp;gt; /map&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Nodes:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;depthimage_to_laserscan_node&#039;&#039; converts the depth image into &#039;&#039;/camera_scan&#039;&#039;.&lt;br /&gt;
* &#039;&#039;rtabmap_sync/rgbd_sync&#039;&#039; synchronises RGB-D data on the real robot.&lt;br /&gt;
* &#039;&#039;rtabmap_odom/icp_odometry&#039;&#039; publishes ICP odometry to &#039;&#039;/loc/rtabmap_odom&#039;&#039; on the real robot.&lt;br /&gt;
* &#039;&#039;rtabmap_slam/rtabmap&#039;&#039; runs either SLAM mode or localisation mode.&lt;br /&gt;
&lt;br /&gt;
In SLAM mode, &#039;&#039;delete_db_on_start=True&#039;&#039; and RTAB-Map is launched with &#039;&#039;-d&#039;&#039;, so the selected database is overwritten. In localisation mode, &#039;&#039;Mem/IncrementalMemory=False&#039;&#039; and &#039;&#039;Mem/InitWMWithAllNodes=True&#039;&#039;, so the existing map is loaded for localisation.&lt;br /&gt;
&lt;br /&gt;
=== launch/map.launch.py ===&lt;br /&gt;
&lt;br /&gt;
Starts a component-based Nav2 map server in namespace &#039;&#039;loc&#039;&#039;, using &#039;&#039;config/nav2_servers.yaml&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
Launch arguments:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;use_sim_time&#039;&#039; - forwarded to the map server.&lt;br /&gt;
* &#039;&#039;map_make_container&#039;&#039;, &#039;&#039;map_container&#039;&#039;, and &#039;&#039;map_container_ns&#039;&#039; - declared as container-related options. The current launch creates a container named &#039;&#039;_map&#039;&#039; with prefix/namespace &#039;&#039;map&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
The map server is configured with:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;topic_name=/loc/map&#039;&#039;&lt;br /&gt;
* &#039;&#039;frame_id=map&#039;&#039;&lt;br /&gt;
* &#039;&#039;yaml_filename=&amp;quot;&amp;quot;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
In the current system, the map is primarily produced by RTAB-Map, while the Nav2 map server exposes map-server functionality for the navigation stack.&lt;br /&gt;
&lt;br /&gt;
=== launch/planning.launch.py ===&lt;br /&gt;
&lt;br /&gt;
Starts the Nav2 planning and navigation servers, the Fejemis velocity relay, the brush service, and optionally the OpenNav coverage server.&lt;br /&gt;
&lt;br /&gt;
Launch arguments:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;namespace&#039;&#039; - namespace for Nav2 nodes. Default: &#039;&#039;fejemis&#039;&#039;.&lt;br /&gt;
* &#039;&#039;use_sim_time&#039;&#039; - forwarded to nodes. Default: &#039;&#039;False&#039;&#039;.&lt;br /&gt;
* &#039;&#039;enable_coverage&#039;&#039; - enables &#039;&#039;opennav_coverage&#039;&#039;. The default is detected from whether the coverage packages are installed.&lt;br /&gt;
&lt;br /&gt;
Nodes:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;nav2_controller/controller_server&#039;&#039; named &#039;&#039;controller&#039;&#039;.&lt;br /&gt;
* &#039;&#039;fejemis_maploc/nav2_cmd_vel_deg_relay.py&#039;&#039;.&lt;br /&gt;
* &#039;&#039;nav2_behaviors/behavior_server&#039;&#039; named &#039;&#039;behavior&#039;&#039;.&lt;br /&gt;
* &#039;&#039;nav2_planner/planner_server&#039;&#039; named &#039;&#039;planner_server&#039;&#039;.&lt;br /&gt;
* &#039;&#039;nav2_bt_navigator/bt_navigator&#039;&#039; named &#039;&#039;bt_navigator&#039;&#039;.&lt;br /&gt;
* &#039;&#039;nav2_map_server/map_server&#039;&#039;.&lt;br /&gt;
* &#039;&#039;fejemis_maploc/brush_control_service.py&#039;&#039; in namespace &#039;&#039;control/brush&#039;&#039;, named &#039;&#039;controller&#039;&#039;.&lt;br /&gt;
* &#039;&#039;opennav_coverage/opennav_coverage&#039;&#039; named &#039;&#039;coverage_server&#039;&#039;, when enabled.&lt;br /&gt;
&lt;br /&gt;
Important remappings:&lt;br /&gt;
&lt;br /&gt;
* Nav2 odometry is remapped from &#039;&#039;/behavior/odom&#039;&#039; to &#039;&#039;/loc/odom&#039;&#039;.&lt;br /&gt;
* Nav2 map input is remapped from &#039;&#039;/behavior/map&#039;&#039; to &#039;&#039;/loc/map&#039;&#039;.&lt;br /&gt;
* Nav2 controller output &#039;&#039;cmd_vel&#039;&#039; is remapped to &#039;&#039;/control/nav2/cmd_vel&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
The velocity relay then converts angular velocity units and republishes to &#039;&#039;/control/joy/cmd_vel&#039;&#039; for the existing Fejemis control path.&lt;br /&gt;
&lt;br /&gt;
The Nav2 configuration in &#039;&#039;config/nav2_servers.yaml&#039;&#039; uses:&lt;br /&gt;
&lt;br /&gt;
* Regulated Pure Pursuit as the path follower.&lt;br /&gt;
* Navfn as the global planner.&lt;br /&gt;
* Local costmap sources from LiDAR, camera scan, net scan, and cable scan.&lt;br /&gt;
* Global costmap with static map, LiDAR obstacle layer, and inflation layer.&lt;br /&gt;
* The OpenNav coverage navigator when coverage support is enabled.&lt;br /&gt;
&lt;br /&gt;
=== launch/lifecycle.launch.py ===&lt;br /&gt;
&lt;br /&gt;
Starts Nav2 lifecycle managers.&lt;br /&gt;
&lt;br /&gt;
Launch arguments:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;in_sim&#039;&#039; - forwarded as &#039;&#039;use_sim_time&#039;&#039;.&lt;br /&gt;
* &#039;&#039;enable_loc_lfc&#039;&#039; - controls whether the localisation lifecycle manager is started. Default: &#039;&#039;false&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
Lifecycle managers:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;/fejemis/beh_lfc&#039;&#039; - behaviour/navigation lifecycle manager.&lt;br /&gt;
* &#039;&#039;/loc/loc_lfc&#039;&#039; - localisation lifecycle manager, only when &#039;&#039;enable_loc_lfc=true&#039;&#039;.&lt;br /&gt;
* &#039;&#039;/fejemis/hard_lfc&#039;&#039; - hardware lifecycle manager, disabled in simulation.&lt;br /&gt;
&lt;br /&gt;
Diagnostics are remapped to &#039;&#039;/diag/beh_lfc&#039;&#039;, &#039;&#039;/diag/loc_lfc&#039;&#039;, and &#039;&#039;/diag/hard_lfc&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
=== launch/lifecycle_managers_only.launch.py ===&lt;br /&gt;
&lt;br /&gt;
Starts the navigation-side launch files without the heavy sensor components. This is useful when another machine is responsible for LiDAR, RealSense, or RTAB-Map.&lt;br /&gt;
&lt;br /&gt;
Launch arguments:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;in_sim&#039;&#039; - enables simulation time.&lt;br /&gt;
* &#039;&#039;localization&#039;&#039; - when true, starts the planning stack on the main Pi. Default: &#039;&#039;false&#039;&#039;.&lt;br /&gt;
* &#039;&#039;enable_coverage&#039;&#039; - enables coverage navigation. Default: &#039;&#039;true&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
Included launch files:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;planning.launch.py&#039;&#039; is included only when &#039;&#039;in_sim=true&#039;&#039; or &#039;&#039;localization=true&#039;&#039;.&lt;br /&gt;
* &#039;&#039;lifecycle.launch.py&#039;&#039; is included immediately.&lt;br /&gt;
* &#039;&#039;behavior.launch.py&#039;&#039; is included after a 30 second delay, giving Nav2 servers time to become active before the BT engine connects to action servers.&lt;br /&gt;
&lt;br /&gt;
=== launch/behavior.launch.py ===&lt;br /&gt;
&lt;br /&gt;
Starts the Fejemis behaviour tree engine from &#039;&#039;raubase_core&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
Launch argument:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;use_sim_time&#039;&#039; - forwarded to the BT engine.&lt;br /&gt;
&lt;br /&gt;
The launch file selects the behaviour tree XML based on the ROS distribution:&lt;br /&gt;
&lt;br /&gt;
* Older BehaviourTree.CPP format: &#039;&#039;resources/behavior/main.xml&#039;&#039;.&lt;br /&gt;
* BehaviourTree.CPP v4 format: &#039;&#039;resources/behavior/main4.xml&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
The node launched is:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;raubase_core/bt_engine&#039;&#039;, configured by &#039;&#039;config/bt_engine.yaml&#039;&#039; in namespace &#039;&#039;fejemis&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
The BT plugin configuration loads:&lt;br /&gt;
&lt;br /&gt;
* raubase logging, mission, blackboard, coverage, and mixer plugins.&lt;br /&gt;
* Nav2 action plugins for single trigger, follow path, drive on heading, spin, and navigate to pose.&lt;br /&gt;
* OpenNav coverage path computation plugin.&lt;br /&gt;
* The package&#039;s custom &#039;&#039;fejemis_call_trigger_service_bt_node&#039;&#039; plugin.&lt;br /&gt;
&lt;br /&gt;
=== launch/robot.launch.py inclusion ===&lt;br /&gt;
&lt;br /&gt;
The top-level &#039;&#039;fejemis&#039;&#039; package includes &#039;&#039;fejemis_maploc&#039;&#039; from &#039;&#039;fejemis/launch/robot.launch.py&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
Typical distribution:&lt;br /&gt;
&lt;br /&gt;
* Main Pi: bridge control, odometry, lifecycle managers, behaviour tree, and planning when localisation is enabled.&lt;br /&gt;
* Auxiliary Pi: LiDAR, RTAB-Map, RealSense, and optional vision.&lt;br /&gt;
&lt;br /&gt;
The top-level launch waits 3 seconds after bridge startup before launching the rest of the stack.&lt;br /&gt;
&lt;br /&gt;
== Script Nodes ==&lt;br /&gt;
&lt;br /&gt;
=== nav2_cmd_vel_deg_relay.py ===&lt;br /&gt;
&lt;br /&gt;
Purpose: adapts Nav2 velocity commands to the Fejemis control convention.&lt;br /&gt;
&lt;br /&gt;
Subscriptions:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;/control/nav2/cmd_vel&#039;&#039; - &#039;&#039;geometry_msgs/Twist&#039;&#039;, with angular velocity in rad/s.&lt;br /&gt;
&lt;br /&gt;
Publications:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;/control/joy/cmd_vel&#039;&#039; - &#039;&#039;geometry_msgs/Twist&#039;&#039;, with angular velocity converted to deg/s.&lt;br /&gt;
&lt;br /&gt;
The linear fields are copied unchanged. The angular x, y, and z fields are multiplied by 180/pi. This is needed because Nav2 uses the normal ROS convention of rad/s, while the existing Fejemis bridge control path expects angular velocity in deg/s.&lt;br /&gt;
&lt;br /&gt;
=== brush_control_service.py ===&lt;br /&gt;
&lt;br /&gt;
Purpose: exposes simple ROS services for turning the brush on and off.&lt;br /&gt;
&lt;br /&gt;
Parameters:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;manage_topic&#039;&#039; - topic for &#039;&#039;fejemis_bridge/msg/SteerManage&#039;&#039;. Default: &#039;&#039;/fejemis/front/manage&#039;&#039;.&lt;br /&gt;
* &#039;&#039;qos_depth&#039;&#039; - publisher QoS depth. Default: &#039;&#039;10&#039;&#039;.&lt;br /&gt;
* &#039;&#039;initial_brush_on&#039;&#039; - initial internal state. Default: &#039;&#039;False&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
Publications:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;manage_topic&#039;&#039; - publishes &#039;&#039;SteerManage.BRUSH_ON&#039;&#039; or &#039;&#039;SteerManage.BRUSH_OFF&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
Services:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;~/set&#039;&#039; - &#039;&#039;std_srvs/SetBool&#039;&#039;, true means brush on and false means brush off.&lt;br /&gt;
* &#039;&#039;~/on&#039;&#039; - &#039;&#039;std_srvs/Trigger&#039;&#039;, turns the brush on.&lt;br /&gt;
* &#039;&#039;~/off&#039;&#039; - &#039;&#039;std_srvs/Trigger&#039;&#039;, turns the brush off.&lt;br /&gt;
&lt;br /&gt;
In &#039;&#039;planning.launch.py&#039;&#039; this node runs as &#039;&#039;/control/brush/controller&#039;&#039;, which creates the services used by the behaviour tree:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;/control/brush/controller/on&#039;&#039;&lt;br /&gt;
* &#039;&#039;/control/brush/controller/off&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
=== rviz_goal_relay.py ===&lt;br /&gt;
&lt;br /&gt;
Purpose: lets an operator send a Nav2 goal from RViz.&lt;br /&gt;
&lt;br /&gt;
Subscriptions:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;/goal_pose&#039;&#039; - RViz 2D Nav Goal as &#039;&#039;geometry_msgs/PoseStamped&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
Action clients:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;/fejemis/navigate_to_pose&#039;&#039; - &#039;&#039;nav2_msgs/action/NavigateToPose&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
When a goal is clicked in RViz, the node checks that the action server is ready, forwards the goal, and logs whether it was accepted and whether it succeeded.&lt;br /&gt;
&lt;br /&gt;
=== mission_trigger.py ===&lt;br /&gt;
&lt;br /&gt;
Purpose: starts a cleaning mission by publishing blackboard values and then setting the active mission.&lt;br /&gt;
&lt;br /&gt;
Parameters:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;mission&#039;&#039; - mission string to publish. Default: &#039;&#039;cleaning/waypoint&#039;&#039;.&lt;br /&gt;
* &#039;&#039;dock_pose&#039;&#039; - dock pose as &#039;&#039;[x, y, yaw]&#039;&#039;. Default: &#039;&#039;[2.62, 3.9, 0.0]&#039;&#039;.&lt;br /&gt;
* &#039;&#039;waypoint_pose&#039;&#039; - waypoint pose as &#039;&#039;[x, y, yaw]&#039;&#039;. Default: &#039;&#039;[2.10491, 0.497438, 3.11127]&#039;&#039;.&lt;br /&gt;
* &#039;&#039;clean_poly&#039;&#039; - polygon string used for cleaning area blackboard values.&lt;br /&gt;
* &#039;&#039;startup_delay&#039;&#039; - delay before publishing. Default: &#039;&#039;2.0&#039;&#039; seconds.&lt;br /&gt;
&lt;br /&gt;
Publications:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;/blackboard/dock_x&#039;&#039;, &#039;&#039;/blackboard/dock_y&#039;&#039;, &#039;&#039;/blackboard/dock_yaw&#039;&#039;&lt;br /&gt;
* &#039;&#039;/blackboard/waypoint_x&#039;&#039;, &#039;&#039;/blackboard/waypoint_y&#039;&#039;, &#039;&#039;/blackboard/waypoint_yaw&#039;&#039;&lt;br /&gt;
* &#039;&#039;/blackboard/field_polygon&#039;&#039;&lt;br /&gt;
* &#039;&#039;/blackboard/clean_poly&#039;&#039;&lt;br /&gt;
* &#039;&#039;/blackboard/start_pose&#039;&#039;&lt;br /&gt;
* &#039;&#039;/blackboard/dock_pose&#039;&#039;&lt;br /&gt;
* &#039;&#039;/blackboard/waypoint_pose&#039;&#039;&lt;br /&gt;
* &#039;&#039;/behavior/set_mission&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The blackboard publishers use transient-local QoS so late subscribers can still receive the latest values. Pose strings are encoded as:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
stamp;frame;x;y;z;qx;qy;qz;qw&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The node also subscribes to &#039;&#039;/behavior/set_mission&#039;&#039;. When it sees &#039;&#039;auto/idle&#039;&#039; after the mission has run, it logs completion and shuts down.&lt;br /&gt;
&lt;br /&gt;
=== complete_coverage_planner.py ===&lt;br /&gt;
&lt;br /&gt;
Purpose: interactive coverage test node for RViz.&lt;br /&gt;
&lt;br /&gt;
Parameters:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;rviz_click_topic&#039;&#039; - clicked point topic. Default: &#039;&#039;/clicked_point&#039;&#039;.&lt;br /&gt;
* &#039;&#039;coverage_frame&#039;&#039; - frame for the coverage polygon. Default: &#039;&#039;map&#039;&#039;.&lt;br /&gt;
* &#039;&#039;min_polygon_points&#039;&#039; - number of clicked points before sending a goal. Default: &#039;&#039;4&#039;&#039;.&lt;br /&gt;
* &#039;&#039;auto_close_polygon&#039;&#039; - whether to append the first point as the final point. Default: &#039;&#039;True&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
Subscriptions:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;/clicked_point&#039;&#039; by default - RViz point clicks.&lt;br /&gt;
&lt;br /&gt;
Action clients:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;fejemis/navigate_complete_coverage&#039;&#039; - &#039;&#039;opennav_coverage_msgs/action/NavigateCompleteCoverage&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
The node waits for &#039;&#039;fejemis/bt_navigator&#039;&#039; to become active, collects clicked points in RViz, converts them to a &#039;&#039;geometry_msgs/Polygon&#039;&#039;, and sends a coverage navigation action once enough points have been collected.&lt;br /&gt;
&lt;br /&gt;
=== fixed_complete_coverage_planner.py ===&lt;br /&gt;
&lt;br /&gt;
Purpose: fixed-polygon coverage test node.&lt;br /&gt;
&lt;br /&gt;
Parameters:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;coverage_frame&#039;&#039; - frame for the coverage polygon. Default: &#039;&#039;map&#039;&#039;.&lt;br /&gt;
* &#039;&#039;min_polygon_points&#039;&#039; - retained from the interactive tester. Default: &#039;&#039;4&#039;&#039;.&lt;br /&gt;
* &#039;&#039;auto_close_polygon&#039;&#039; - whether to append the first point as the final point. Default: &#039;&#039;True&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
Action clients:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;fejemis/navigate_complete_coverage&#039;&#039; - &#039;&#039;opennav_coverage_msgs/action/NavigateCompleteCoverage&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
The node waits for &#039;&#039;fejemis/bt_navigator&#039;&#039; to become active, then sends a fixed four-point polygon:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
(-5.35,  2.45)&lt;br /&gt;
( 2.83,  3.18)&lt;br /&gt;
( 3.22, -5.08)&lt;br /&gt;
(-4.76, -5.97)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This is useful for testing coverage planning without clicking points manually in RViz.&lt;br /&gt;
&lt;br /&gt;
== C++ Behaviour Tree Plugin ==&lt;br /&gt;
&lt;br /&gt;
=== fejemis_call_trigger_service_bt_node ===&lt;br /&gt;
&lt;br /&gt;
The C++ plugin &#039;&#039;src/bt_plugins/call_trigger_service_bt_node.cpp&#039;&#039; registers the behaviour tree node &#039;&#039;CallTriggerService&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
Inputs:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;service_name&#039;&#039; - service to call.&lt;br /&gt;
* &#039;&#039;wait_for_service_ms&#039;&#039; - timeout while waiting for service availability. Default: &#039;&#039;1000&#039;&#039;.&lt;br /&gt;
* &#039;&#039;call_timeout_ms&#039;&#039; - timeout for the service call. Default: &#039;&#039;2000&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
The node creates a &#039;&#039;std_srvs/Trigger&#039;&#039; client, waits for the target service, calls it, and returns:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;SUCCESS&#039;&#039; when the service exists, responds before timeout, and returns &#039;&#039;success=true&#039;&#039;.&lt;br /&gt;
* &#039;&#039;FAILURE&#039;&#039; when the service is unavailable, the call times out, or the response reports failure.&lt;br /&gt;
&lt;br /&gt;
The cleaning subtree uses it to call:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;/control/brush/controller/on&#039;&#039;&lt;br /&gt;
* &#039;&#039;/control/brush/controller/off&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
== Behaviour Tree ==&lt;br /&gt;
&lt;br /&gt;
The active behaviour tree on newer ROS distributions is &#039;&#039;resources/behavior/main4.xml&#039;&#039;. It is loaded by &#039;&#039;behavior.launch.py&#039;&#039; through the &#039;&#039;raubase_core/bt_engine&#039;&#039; node.&lt;br /&gt;
&lt;br /&gt;
=== Main Loop ===&lt;br /&gt;
&lt;br /&gt;
The root tree is &#039;&#039;main&#039;&#039;:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
main&lt;br /&gt;
`-- Sequence&lt;br /&gt;
    |-- SingleTrigger&lt;br /&gt;
    |   `-- t_init&lt;br /&gt;
    `-- KeepRunningUntilFailure&lt;br /&gt;
        `-- ReactiveSequence&lt;br /&gt;
            |-- ForceSuccess&lt;br /&gt;
            |   `-- t_passive&lt;br /&gt;
            `-- t_runtime&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The structure means:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;t_init&#039;&#039; runs once at startup.&lt;br /&gt;
* &#039;&#039;t_passive&#039;&#039; runs repeatedly and updates blackboard/mission state.&lt;br /&gt;
* &#039;&#039;t_runtime&#039;&#039; selects the active behaviour based on the current mission.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Visualization placeholder:&#039;&#039;&#039; exported Groot image of &#039;&#039;main4.xml&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
=== t_init ===&lt;br /&gt;
&lt;br /&gt;
Initialises the robot behaviour state:&lt;br /&gt;
&lt;br /&gt;
* Sets mission to &#039;&#039;auto/idle&#039;&#039;.&lt;br /&gt;
* Sets blackboard key &#039;&#039;initialized=1&#039;&#039;.&lt;br /&gt;
* Sets calibration square distance &#039;&#039;calib_sq_dist=7&#039;&#039;.&lt;br /&gt;
* Opens the mixer port using &#039;&#039;OpenMixerPort&#039;&#039; with priority 4 and topic &#039;&#039;/behavior&#039;&#039;.&lt;br /&gt;
* Logs that the robot is initialized.&lt;br /&gt;
&lt;br /&gt;
=== t_passive ===&lt;br /&gt;
&lt;br /&gt;
Runs as the passive safety/update loop:&lt;br /&gt;
&lt;br /&gt;
* Logs &#039;&#039;Safety loop&#039;&#039; at debug level.&lt;br /&gt;
* Refreshes &#039;&#039;start_pose&#039;&#039;, &#039;&#039;waypoint_pose&#039;&#039;, &#039;&#039;clean_poly&#039;&#039;, and &#039;&#039;field_polygon&#039;&#039; from &#039;&#039;/blackboard/...&#039;&#039; topics.&lt;br /&gt;
* Refreshes the current mission from &#039;&#039;/behavior/set_mission&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
=== t_runtime ===&lt;br /&gt;
&lt;br /&gt;
Selects behaviour using a reactive fallback:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
t_runtime&lt;br /&gt;
|-- t_idle_runtime&lt;br /&gt;
|-- t_calib&lt;br /&gt;
|-- t_mapping&lt;br /&gt;
|-- t_cleaning&lt;br /&gt;
`-- unknown mission handler -&amp;gt; set idle&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The first matching mission branch runs. If no known branch matches, the tree logs an error and returns to idle.&lt;br /&gt;
&lt;br /&gt;
=== Idle ===&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;t_idle_runtime&#039;&#039; succeeds when the current mission is:&lt;br /&gt;
&lt;br /&gt;
* mission: &#039;&#039;auto&#039;&#039;&lt;br /&gt;
* mission_sub: &#039;&#039;idle&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
This keeps the robot in a waiting state. Sensors, telemetry, and subscriptions remain active, but no movement commands are issued. The robot leaves this state when a new mission is received through &#039;&#039;/behavior/set_mission&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
=== Calibration ===&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;t_calib&#039;&#039; chooses between line calibration and square calibration.&lt;br /&gt;
&lt;br /&gt;
Line calibration, &#039;&#039;t_calib_line&#039;&#039;:&lt;br /&gt;
&lt;br /&gt;
* Requires mission &#039;&#039;calib/line&#039;&#039;.&lt;br /&gt;
* Drives forward using &#039;&#039;DriveOnHeading&#039;&#039; &lt;br /&gt;
* &#039;&#039;calib_sq_dist&#039;&#039; is set to 0.05 m/s during initialization.&lt;br /&gt;
* Returns to idle.&lt;br /&gt;
&lt;br /&gt;
Square calibration, &#039;&#039;t_calib_square&#039;&#039;:&lt;br /&gt;
&lt;br /&gt;
* Requires mission &#039;&#039;calib/*&#039;&#039;.&lt;br /&gt;
* Runs &#039;&#039;f_calib_side&#039;&#039; four times.&lt;br /&gt;
* Each side drives forward and spins 1.57 rad.&lt;br /&gt;
* Returns to idle.&lt;br /&gt;
Each &#039;&#039;f_calib_side&#039;&#039; contains:&lt;br /&gt;
&lt;br /&gt;
*DriveOnHeading&lt;br /&gt;
*Spin&lt;br /&gt;
*A quarter-turn style calibration step&lt;br /&gt;
Calibration succeeds when each action in the sequence completes successfully.&lt;br /&gt;
&lt;br /&gt;
=== Mapping ===&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;t_mapping&#039;&#039; currently checks for &#039;&#039;mission mapping/*&#039;&#039; and then returns to idle.&lt;br /&gt;
&lt;br /&gt;
*Requires mission mapping/*&lt;br /&gt;
&lt;br /&gt;
*Currently acts as a placeholder&lt;br /&gt;
&lt;br /&gt;
*Does not yet launch the SLAM or exploration pipeline&lt;br /&gt;
&lt;br /&gt;
*Returns to idle immediately&lt;br /&gt;
&lt;br /&gt;
This branch should later be expanded to perform autonomous mapping, localization checks, and map saving.&lt;br /&gt;
&lt;br /&gt;
=== Cleaning ===&lt;br /&gt;
&lt;br /&gt;
The current cleaning branch is &#039;&#039;t_cleaning&#039;&#039;:&lt;br /&gt;
&lt;br /&gt;
* Requires mission &#039;&#039;cleaning/waypoint&#039;&#039; with mission_sub &#039;&#039;idle&#039;&#039;.&lt;br /&gt;
* Navigates to &#039;&#039;{waypoint_pose}&#039;&#039; using &#039;&#039;NavigateToPose&#039;&#039;.&lt;br /&gt;
* Sets current mission to &#039;&#039;cleaning/area&#039;&#039;.&lt;br /&gt;
* Returns to idle.&lt;br /&gt;
&lt;br /&gt;
Supporting cleaning subtrees are already present:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;f_get_area_to_clean&#039;&#039; sets a polygon and calls &#039;&#039;ComputeCoveragePath&#039;&#039;.&lt;br /&gt;
* &#039;&#039;f_clean_area&#039;&#039; turns the brush on, follows &#039;&#039;{cover_nav_path}&#039;&#039;, and turns the brush off.&lt;br /&gt;
* &#039;&#039;f_go_to_path_start&#039;&#039; is a placeholder that logs navigation to the path start.&lt;br /&gt;
&lt;br /&gt;
These subtrees show the intended full cleaning sequence, although the active &#039;&#039;t_cleaning&#039;&#039; branch currently only navigates to a waypoint and changes mission state.&lt;br /&gt;
[[File:BT-cleaning.jepg| 800px]]&lt;br /&gt;
&lt;br /&gt;
Cleaning Node Behaviour Sequence: mission trigger -&amp;gt; waypoint navigation -&amp;gt; coverage path computation -&amp;gt; brush on -&amp;gt; follow coverage path -&amp;gt; brush off -&amp;gt; idle.&lt;br /&gt;
&lt;br /&gt;
== Configuration Files ==&lt;br /&gt;
&lt;br /&gt;
=== config/odometry.yaml and config/odometry_sim.yaml ===&lt;br /&gt;
&lt;br /&gt;
Configure the EKF and IMU filter. The real configuration fuses wheel odometry and RTAB-Map ICP odometry in 2D mode. The simulation configuration is separated to avoid conflicting simulated IMU orientation.&lt;br /&gt;
&lt;br /&gt;
=== config/rtabmap.yaml and config/rtabmap_sim.yaml ===&lt;br /&gt;
&lt;br /&gt;
Configure RTAB-Map for real robot and simulation use. &#039;&#039;rtabmap_slam.launch.py&#039;&#039; selects between them using &#039;&#039;use_sim_time&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
=== config/nav2_servers.yaml and config/nav2_sim.yaml ===&lt;br /&gt;
&lt;br /&gt;
Configure Nav2 controller, planner, behaviour server, costmaps, BT navigator, coverage server, and map server. The main real-robot launch uses &#039;&#039;nav2_servers.yaml&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
=== config/bt_engine.yaml ===&lt;br /&gt;
&lt;br /&gt;
Lists behaviour tree plugins loaded by &#039;&#039;raubase_core/bt_engine&#039;&#039;, including the Fejemis custom trigger-service plugin.&lt;br /&gt;
&lt;br /&gt;
=== config/lifecycle.yaml ===&lt;br /&gt;
&lt;br /&gt;
Configures lifecycle manager node lists and autostart behaviour for the navigation, localisation, and hardware lifecycle managers.&lt;br /&gt;
&lt;br /&gt;
=== config/realsense.yaml ===&lt;br /&gt;
&lt;br /&gt;
RealSense camera configuration loaded by &#039;&#039;realsense.launch.py&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
== Typical Usage ==&lt;br /&gt;
&lt;br /&gt;
Start the complete robot stack through the top-level launch:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ros2 launch fejemis robot.launch.py localization:=true map_location:=asta&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Run on both Raspberry Pis. The launch file decides which parts run on the main and auxiliary computers through the &#039;&#039;is_aux&#039;&#039; argument.&lt;br /&gt;
&lt;br /&gt;
Start a waypoint cleaning mission after the system is up:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ros2 run fejemis_maploc mission_trigger.py&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Send an RViz navigation goal:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ros2 run fejemis_maploc rviz_goal_relay.py&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Test coverage navigation with RViz clicked points:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ros2 run fejemis_maploc complete_coverage_planner.py&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Test coverage navigation with the fixed polygon:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ros2 run fejemis_maploc fixed_complete_coverage_planner.py&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Notes and Future Work ==&lt;br /&gt;
&lt;br /&gt;
* Clarify which Raspberry Pi should run each launch file in the final deployment.&lt;br /&gt;
* Expand the cleaning behaviour section once &#039;&#039;t_cleaning&#039;&#039; is connected to &#039;&#039;f_get_area_to_clean&#039;&#039; and &#039;&#039;f_clean_area&#039;&#039; in the active tree.&lt;br /&gt;
* Test and validate the complete cleaning behaviour structure in the final hardware setup, as it has not yet been fully evaluated due to current Raspberry Pi limitations.&lt;/div&gt;</summary>
		<author><name>S252724</name></author>
	</entry>
	<entry>
		<id>https://rsewiki.electro.dtu.dk/index.php?title=File:BT-cleaning.jpeg&amp;diff=8981</id>
		<title>File:BT-cleaning.jpeg</title>
		<link rel="alternate" type="text/html" href="https://rsewiki.electro.dtu.dk/index.php?title=File:BT-cleaning.jpeg&amp;diff=8981"/>
		<updated>2026-05-28T11:40:02Z</updated>

		<summary type="html">&lt;p&gt;S252724: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>S252724</name></author>
	</entry>
	<entry>
		<id>https://rsewiki.electro.dtu.dk/index.php?title=Mechanics&amp;diff=8980</id>
		<title>Mechanics</title>
		<link rel="alternate" type="text/html" href="https://rsewiki.electro.dtu.dk/index.php?title=Mechanics&amp;diff=8980"/>
		<updated>2026-05-28T11:12:59Z</updated>

		<summary type="html">&lt;p&gt;S252724: /* Main Mechanical Parts */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Back to [[Fejemis_2026]]&lt;br /&gt;
&lt;br /&gt;
== General Description ==&lt;br /&gt;
&lt;br /&gt;
Fejemis is a three wheels mobile robot. The front part in yellow and black is a commercial brush for industry floor. The manual handle has been replaced with the Fejemis drive system. The front wheel can be lowered to allow the brush to be lifted when not in use.&lt;br /&gt;
&lt;br /&gt;
It has been modeled in the cad program OnShape, as you can see in the next figure. &lt;br /&gt;
&lt;br /&gt;
[[File:fejemis-from-onshape.png | 600px]]&lt;br /&gt;
&lt;br /&gt;
The drawings can be accessed here: https://cad.onshape.com/documents/c3f44096b0f5e4646ed48e3f/w/670467f68cac02b087631e3d/e/e591dbcff8879adc4528ee2b&lt;br /&gt;
== Main Mechanical Parts ==&lt;br /&gt;
&lt;br /&gt;
=== Front Wheel ===&lt;br /&gt;
The front wheel plays an important role in the robot’s movement and overall cleaning performance. It helps guide and steer the robot as it moves through the environment, allowing it to turn, follow paths, and navigate around obstacles while cleaning. As a result, the stability and rigidity of the front wheel directly affect both motion accuracy and mapping quality.&lt;br /&gt;
&lt;br /&gt;
==== Previous Design ====&lt;br /&gt;
The original front wheel assembly was one of the main mechanical weaknesses of the robot. Its low rigidity caused wobbling during motion, which negatively affected odometry and reduced movement stability. This introduced drift in the generated map and lowered the overall quality of localization and navigation.&lt;br /&gt;
&lt;br /&gt;
==== Current Design ====&lt;br /&gt;
The wheel assembly was redesigned by adding ball bearings, increasing the infill of the 3D-printed parts, and mounting the magnetic encoder more securely. These changes improved rigidity, produced steadier motion, and reduced map drift.&lt;br /&gt;
&lt;br /&gt;
The updated design is available at this link: https://cad.onshape.com/documents/d35a3b26eb917ad7000d2d0c/w/821c3ada1e18c2055d92ea08/e/35b8a6b1c67d3640704aa6e7 as a downloadable ZIP file for future modification and improvement.&lt;br /&gt;
&lt;br /&gt;
==== Future Work ====&lt;br /&gt;
Although the redesigned front wheel performs better, the current structure still relies on plastic components that may loosen, deform, or wear over time during repeated use. Future work should evaluate whether a tighter mechanical design, a stronger filament material, or a different material altogether would provide better long-term rigidity and durability.&lt;br /&gt;
&lt;br /&gt;
Another component that should be improved is the support that attaches the linear actuator to the robot. This part should be reprinted with higher infill and thicker side walls to increase its strength and reduce flex during operation. Future team members can assess whether these further changes are necessary based on long-term use and observed wear.&lt;/div&gt;</summary>
		<author><name>S252724</name></author>
	</entry>
	<entry>
		<id>https://rsewiki.electro.dtu.dk/index.php?title=Mechanics&amp;diff=8979</id>
		<title>Mechanics</title>
		<link rel="alternate" type="text/html" href="https://rsewiki.electro.dtu.dk/index.php?title=Mechanics&amp;diff=8979"/>
		<updated>2026-05-28T11:06:14Z</updated>

		<summary type="html">&lt;p&gt;S252724: /* Front Wheel Design */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Back to [[Fejemis_2026]]&lt;br /&gt;
&lt;br /&gt;
== General Description ==&lt;br /&gt;
&lt;br /&gt;
Fejemis is a three wheels mobile robot. The front part in yellow and black is a commercial brush for industry floor. The manual handle has been replaced with the Fejemis drive system. The front wheel can be lowered to allow the brush to be lifted when not in use.&lt;br /&gt;
&lt;br /&gt;
It has been modeled in the cad program OnShape, as you can see in the next figure. &lt;br /&gt;
&lt;br /&gt;
[[File:fejemis-from-onshape.png | 600px]]&lt;br /&gt;
&lt;br /&gt;
The drawings can be accessed here: https://cad.onshape.com/documents/c3f44096b0f5e4646ed48e3f/w/670467f68cac02b087631e3d/e/e591dbcff8879adc4528ee2b&lt;br /&gt;
== Main Mechanical Parts ==&lt;/div&gt;</summary>
		<author><name>S252724</name></author>
	</entry>
	<entry>
		<id>https://rsewiki.electro.dtu.dk/index.php?title=Mechanics&amp;diff=8978</id>
		<title>Mechanics</title>
		<link rel="alternate" type="text/html" href="https://rsewiki.electro.dtu.dk/index.php?title=Mechanics&amp;diff=8978"/>
		<updated>2026-05-28T11:05:39Z</updated>

		<summary type="html">&lt;p&gt;S252724: /* Front Wheel Design */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Back to [[Fejemis_2026]]&lt;br /&gt;
&lt;br /&gt;
== General Description ==&lt;br /&gt;
&lt;br /&gt;
Fejemis is a three wheels mobile robot. The front part in yellow and black is a commercial brush for industry floor. The manual handle has been replaced with the Fejemis drive system. The front wheel can be lowered to allow the brush to be lifted when not in use.&lt;br /&gt;
&lt;br /&gt;
It has been modeled in the cad program OnShape, as you can see in the next figure. &lt;br /&gt;
&lt;br /&gt;
[[File:fejemis-from-onshape.png | 600px]]&lt;br /&gt;
&lt;br /&gt;
The drawings can be accessed here: https://cad.onshape.com/documents/c3f44096b0f5e4646ed48e3f/w/670467f68cac02b087631e3d/e/e591dbcff8879adc4528ee2b&lt;br /&gt;
== Main Mechanical Parts ==&lt;br /&gt;
&lt;br /&gt;
== Front Wheel Design ==&lt;br /&gt;
=== Previous Design === &lt;br /&gt;
The original front wheel assembly was one of the main mechanical weaknesses of the robot. Its low rigidity caused wobbling during motion, which negatively affected odometry and reduced movement stability. This introduced drift in the generated map and lowered the overall quality of localization and navigation.&lt;br /&gt;
=== Current Design === &lt;br /&gt;
The wheel assembly was redesigned by adding ball bearings, increasing the infill of the 3D-printed parts, and mounting the magnetic encoder more securely. These changes improved rigidity, produced steadier motion, and reduced map drift.&lt;br /&gt;
&lt;br /&gt;
The updated design is available on this website as a downloadable ZIP file for future modification and improvement.&lt;br /&gt;
&lt;br /&gt;
=== Future Works === &lt;br /&gt;
Although the redesigned front wheel performs better, the current structure still relies on plastic components that may loosen, deform, or wear over time during repeated use. Future work should evaluate whether a tighter mechanical design, a stronger filament material, or a different material altogether would provide better long-term rigidity and durability.&lt;br /&gt;
&lt;br /&gt;
Another component that should be improved is the support that attaches the linear actuator to the robot. This part should be reprinted with higher infill and thicker side walls to increase its strength and reduce flex during operation. Future team members can assess whether these further changes are necessary based on long-term use and observed wear.&lt;/div&gt;</summary>
		<author><name>S252724</name></author>
	</entry>
	<entry>
		<id>https://rsewiki.electro.dtu.dk/index.php?title=Mechanics&amp;diff=8977</id>
		<title>Mechanics</title>
		<link rel="alternate" type="text/html" href="https://rsewiki.electro.dtu.dk/index.php?title=Mechanics&amp;diff=8977"/>
		<updated>2026-05-28T11:05:25Z</updated>

		<summary type="html">&lt;p&gt;S252724: /* Main Mechanical Parts */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Back to [[Fejemis_2026]]&lt;br /&gt;
&lt;br /&gt;
== General Description ==&lt;br /&gt;
&lt;br /&gt;
Fejemis is a three wheels mobile robot. The front part in yellow and black is a commercial brush for industry floor. The manual handle has been replaced with the Fejemis drive system. The front wheel can be lowered to allow the brush to be lifted when not in use.&lt;br /&gt;
&lt;br /&gt;
It has been modeled in the cad program OnShape, as you can see in the next figure. &lt;br /&gt;
&lt;br /&gt;
[[File:fejemis-from-onshape.png | 600px]]&lt;br /&gt;
&lt;br /&gt;
The drawings can be accessed here: https://cad.onshape.com/documents/c3f44096b0f5e4646ed48e3f/w/670467f68cac02b087631e3d/e/e591dbcff8879adc4528ee2b&lt;br /&gt;
== Main Mechanical Parts ==&lt;br /&gt;
&lt;br /&gt;
=== Front Wheel Design ===&lt;/div&gt;</summary>
		<author><name>S252724</name></author>
	</entry>
	<entry>
		<id>https://rsewiki.electro.dtu.dk/index.php?title=Mechanics&amp;diff=8976</id>
		<title>Mechanics</title>
		<link rel="alternate" type="text/html" href="https://rsewiki.electro.dtu.dk/index.php?title=Mechanics&amp;diff=8976"/>
		<updated>2026-05-28T11:05:10Z</updated>

		<summary type="html">&lt;p&gt;S252724: /* Front Wheel Design */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Back to [[Fejemis_2026]]&lt;br /&gt;
&lt;br /&gt;
== General Description ==&lt;br /&gt;
&lt;br /&gt;
Fejemis is a three wheels mobile robot. The front part in yellow and black is a commercial brush for industry floor. The manual handle has been replaced with the Fejemis drive system. The front wheel can be lowered to allow the brush to be lifted when not in use.&lt;br /&gt;
&lt;br /&gt;
It has been modeled in the cad program OnShape, as you can see in the next figure. &lt;br /&gt;
&lt;br /&gt;
[[File:fejemis-from-onshape.png | 600px]]&lt;br /&gt;
&lt;br /&gt;
The drawings can be accessed here: https://cad.onshape.com/documents/c3f44096b0f5e4646ed48e3f/w/670467f68cac02b087631e3d/e/e591dbcff8879adc4528ee2b&lt;br /&gt;
== Main Mechanical Parts ==&lt;br /&gt;
&lt;br /&gt;
=== Front Wheel Design ===&lt;br /&gt;
&lt;br /&gt;
=== Previous Design === &lt;br /&gt;
The original front wheel assembly was one of the main mechanical weaknesses of the robot. Its low rigidity caused wobbling during motion, which negatively affected odometry and reduced movement stability. This introduced drift in the generated map and lowered the overall quality of localization and navigation.&lt;br /&gt;
=== Current Design === &lt;br /&gt;
The wheel assembly was redesigned by adding ball bearings, increasing the infill of the 3D-printed parts, and mounting the magnetic encoder more securely. These changes improved rigidity, produced steadier motion, and reduced map drift.&lt;br /&gt;
&lt;br /&gt;
The updated design is available on this website as a downloadable ZIP file for future modification and improvement.&lt;br /&gt;
&lt;br /&gt;
=== Future Works === &lt;br /&gt;
Although the redesigned front wheel performs better, the current structure still relies on plastic components that may loosen, deform, or wear over time during repeated use. Future work should evaluate whether a tighter mechanical design, a stronger filament material, or a different material altogether would provide better long-term rigidity and durability.&lt;br /&gt;
&lt;br /&gt;
Another component that should be improved is the support that attaches the linear actuator to the robot. This part should be reprinted with higher infill and thicker side walls to increase its strength and reduce flex during operation. Future team members can assess whether these further changes are necessary based on long-term use and observed wear.&lt;/div&gt;</summary>
		<author><name>S252724</name></author>
	</entry>
	<entry>
		<id>https://rsewiki.electro.dtu.dk/index.php?title=Mechanics&amp;diff=8975</id>
		<title>Mechanics</title>
		<link rel="alternate" type="text/html" href="https://rsewiki.electro.dtu.dk/index.php?title=Mechanics&amp;diff=8975"/>
		<updated>2026-05-28T11:05:01Z</updated>

		<summary type="html">&lt;p&gt;S252724: /* Front Wheel Design */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Back to [[Fejemis_2026]]&lt;br /&gt;
&lt;br /&gt;
== General Description ==&lt;br /&gt;
&lt;br /&gt;
Fejemis is a three wheels mobile robot. The front part in yellow and black is a commercial brush for industry floor. The manual handle has been replaced with the Fejemis drive system. The front wheel can be lowered to allow the brush to be lifted when not in use.&lt;br /&gt;
&lt;br /&gt;
It has been modeled in the cad program OnShape, as you can see in the next figure. &lt;br /&gt;
&lt;br /&gt;
[[File:fejemis-from-onshape.png | 600px]]&lt;br /&gt;
&lt;br /&gt;
The drawings can be accessed here: https://cad.onshape.com/documents/c3f44096b0f5e4646ed48e3f/w/670467f68cac02b087631e3d/e/e591dbcff8879adc4528ee2b&lt;br /&gt;
== Main Mechanical Parts ==&lt;br /&gt;
&lt;br /&gt;
=== Front Wheel Design ===&lt;br /&gt;
=== Previous Design === &lt;br /&gt;
The original front wheel assembly was one of the main mechanical weaknesses of the robot. Its low rigidity caused wobbling during motion, which negatively affected odometry and reduced movement stability. This introduced drift in the generated map and lowered the overall quality of localization and navigation.&lt;br /&gt;
=== Current Design === &lt;br /&gt;
The wheel assembly was redesigned by adding ball bearings, increasing the infill of the 3D-printed parts, and mounting the magnetic encoder more securely. These changes improved rigidity, produced steadier motion, and reduced map drift.&lt;br /&gt;
&lt;br /&gt;
The updated design is available on this website as a downloadable ZIP file for future modification and improvement.&lt;br /&gt;
&lt;br /&gt;
=== Future Works === &lt;br /&gt;
Although the redesigned front wheel performs better, the current structure still relies on plastic components that may loosen, deform, or wear over time during repeated use. Future work should evaluate whether a tighter mechanical design, a stronger filament material, or a different material altogether would provide better long-term rigidity and durability.&lt;br /&gt;
&lt;br /&gt;
Another component that should be improved is the support that attaches the linear actuator to the robot. This part should be reprinted with higher infill and thicker side walls to increase its strength and reduce flex during operation. Future team members can assess whether these further changes are necessary based on long-term use and observed wear.&lt;/div&gt;</summary>
		<author><name>S252724</name></author>
	</entry>
	<entry>
		<id>https://rsewiki.electro.dtu.dk/index.php?title=Mechanics&amp;diff=8974</id>
		<title>Mechanics</title>
		<link rel="alternate" type="text/html" href="https://rsewiki.electro.dtu.dk/index.php?title=Mechanics&amp;diff=8974"/>
		<updated>2026-05-28T11:04:54Z</updated>

		<summary type="html">&lt;p&gt;S252724: /* Main Mechanical Parts */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Back to [[Fejemis_2026]]&lt;br /&gt;
&lt;br /&gt;
== General Description ==&lt;br /&gt;
&lt;br /&gt;
Fejemis is a three wheels mobile robot. The front part in yellow and black is a commercial brush for industry floor. The manual handle has been replaced with the Fejemis drive system. The front wheel can be lowered to allow the brush to be lifted when not in use.&lt;br /&gt;
&lt;br /&gt;
It has been modeled in the cad program OnShape, as you can see in the next figure. &lt;br /&gt;
&lt;br /&gt;
[[File:fejemis-from-onshape.png | 600px]]&lt;br /&gt;
&lt;br /&gt;
The drawings can be accessed here: https://cad.onshape.com/documents/c3f44096b0f5e4646ed48e3f/w/670467f68cac02b087631e3d/e/e591dbcff8879adc4528ee2b&lt;br /&gt;
== Main Mechanical Parts ==&lt;br /&gt;
&lt;br /&gt;
=== Front Wheel Design ===&lt;/div&gt;</summary>
		<author><name>S252724</name></author>
	</entry>
	<entry>
		<id>https://rsewiki.electro.dtu.dk/index.php?title=Mechanics&amp;diff=8973</id>
		<title>Mechanics</title>
		<link rel="alternate" type="text/html" href="https://rsewiki.electro.dtu.dk/index.php?title=Mechanics&amp;diff=8973"/>
		<updated>2026-05-28T11:04:45Z</updated>

		<summary type="html">&lt;p&gt;S252724: /* Front Wheel Design */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Back to [[Fejemis_2026]]&lt;br /&gt;
&lt;br /&gt;
== General Description ==&lt;br /&gt;
&lt;br /&gt;
Fejemis is a three wheels mobile robot. The front part in yellow and black is a commercial brush for industry floor. The manual handle has been replaced with the Fejemis drive system. The front wheel can be lowered to allow the brush to be lifted when not in use.&lt;br /&gt;
&lt;br /&gt;
It has been modeled in the cad program OnShape, as you can see in the next figure. &lt;br /&gt;
&lt;br /&gt;
[[File:fejemis-from-onshape.png | 600px]]&lt;br /&gt;
&lt;br /&gt;
The drawings can be accessed here: https://cad.onshape.com/documents/c3f44096b0f5e4646ed48e3f/w/670467f68cac02b087631e3d/e/e591dbcff8879adc4528ee2b&lt;br /&gt;
== Main Mechanical Parts ==&lt;br /&gt;
&lt;br /&gt;
=== Front Wheel Design ===&lt;br /&gt;
&lt;br /&gt;
=== Previous Design === &lt;br /&gt;
The original front wheel assembly was one of the main mechanical weaknesses of the robot. Its low rigidity caused wobbling during motion, which negatively affected odometry and reduced movement stability. This introduced drift in the generated map and lowered the overall quality of localization and navigation.&lt;br /&gt;
=== Current Design === &lt;br /&gt;
The wheel assembly was redesigned by adding ball bearings, increasing the infill of the 3D-printed parts, and mounting the magnetic encoder more securely. These changes improved rigidity, produced steadier motion, and reduced map drift.&lt;br /&gt;
&lt;br /&gt;
The updated design is available on this website as a downloadable ZIP file for future modification and improvement.&lt;br /&gt;
&lt;br /&gt;
=== Future Works === &lt;br /&gt;
Although the redesigned front wheel performs better, the current structure still relies on plastic components that may loosen, deform, or wear over time during repeated use. Future work should evaluate whether a tighter mechanical design, a stronger filament material, or a different material altogether would provide better long-term rigidity and durability.&lt;br /&gt;
&lt;br /&gt;
Another component that should be improved is the support that attaches the linear actuator to the robot. This part should be reprinted with higher infill and thicker side walls to increase its strength and reduce flex during operation. Future team members can assess whether these further changes are necessary based on long-term use and observed wear.&lt;/div&gt;</summary>
		<author><name>S252724</name></author>
	</entry>
	<entry>
		<id>https://rsewiki.electro.dtu.dk/index.php?title=Mechanics&amp;diff=8972</id>
		<title>Mechanics</title>
		<link rel="alternate" type="text/html" href="https://rsewiki.electro.dtu.dk/index.php?title=Mechanics&amp;diff=8972"/>
		<updated>2026-05-28T11:02:53Z</updated>

		<summary type="html">&lt;p&gt;S252724: /* Front Wheel Design */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Back to [[Fejemis_2026]]&lt;br /&gt;
&lt;br /&gt;
== General Description ==&lt;br /&gt;
&lt;br /&gt;
Fejemis is a three wheels mobile robot. The front part in yellow and black is a commercial brush for industry floor. The manual handle has been replaced with the Fejemis drive system. The front wheel can be lowered to allow the brush to be lifted when not in use.&lt;br /&gt;
&lt;br /&gt;
It has been modeled in the cad program OnShape, as you can see in the next figure. &lt;br /&gt;
&lt;br /&gt;
[[File:fejemis-from-onshape.png | 600px]]&lt;br /&gt;
&lt;br /&gt;
The drawings can be accessed here: https://cad.onshape.com/documents/c3f44096b0f5e4646ed48e3f/w/670467f68cac02b087631e3d/e/e591dbcff8879adc4528ee2b&lt;br /&gt;
== Main Mechanical Parts ==&lt;br /&gt;
&lt;br /&gt;
=== Front Wheel Design ===&lt;br /&gt;
=== Previous Design === &lt;br /&gt;
The original front wheel assembly was one of the main mechanical weaknesses of the robot. Its low rigidity caused wobbling during motion, which negatively affected odometry and reduced movement stability. This introduced drift in the generated map and lowered the overall quality of localization and navigation.&lt;br /&gt;
=== Current Design === &lt;br /&gt;
The wheel assembly was redesigned by adding ball bearings, increasing the infill of the 3D-printed parts, and mounting the magnetic encoder more securely. These changes improved rigidity, produced steadier motion, and reduced map drift.&lt;br /&gt;
&lt;br /&gt;
The updated design is available on this website as a downloadable ZIP file for future modification and improvement.&lt;br /&gt;
&lt;br /&gt;
=== Future Works === &lt;br /&gt;
Although the redesigned front wheel performs better, the current structure still relies on plastic components that may loosen, deform, or wear over time during repeated use. Future work should evaluate whether a tighter mechanical design, a stronger filament material, or a different material altogether would provide better long-term rigidity and durability.&lt;br /&gt;
&lt;br /&gt;
Another component that should be improved is the support that attaches the linear actuator to the robot. This part should be reprinted with higher infill and thicker side walls to increase its strength and reduce flex during operation. Future team members can assess whether these further changes are necessary based on long-term use and observed wear.&lt;/div&gt;</summary>
		<author><name>S252724</name></author>
	</entry>
	<entry>
		<id>https://rsewiki.electro.dtu.dk/index.php?title=Fejemis_Maploc_Ros2&amp;diff=8971</id>
		<title>Fejemis Maploc Ros2</title>
		<link rel="alternate" type="text/html" href="https://rsewiki.electro.dtu.dk/index.php?title=Fejemis_Maploc_Ros2&amp;diff=8971"/>
		<updated>2026-05-28T10:29:39Z</updated>

		<summary type="html">&lt;p&gt;S252724: /* Notes and Future Work */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Back to [[Fejemis ROS2 Software]]&lt;br /&gt;
&lt;br /&gt;
== Purpose ==&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fejemis_maploc&#039;&#039; is the mapping, localisation, navigation, and behaviour package for Fejemis. It connects the robot&#039;s sensor data to the navigation stack and exposes the high-level behaviour tree used to run calibration, mapping, waypoint navigation, and cleaning missions.&lt;br /&gt;
&lt;br /&gt;
The package sits between the low-level bridge and the high-level mission logic:&lt;br /&gt;
&lt;br /&gt;
* The bridge publishes wheel odometry, IMU data, and accepts velocity / brush commands.&lt;br /&gt;
* &#039;&#039;fejemis_maploc&#039;&#039; filters odometry, starts LiDAR and RealSense sensing, runs RTAB-Map, starts Nav2, and runs the Fejemis behaviour tree.&lt;br /&gt;
* The behaviour tree sends commands to Nav2, the coverage planner, the mixer, and the brush service.&lt;br /&gt;
[[File:maploc_general_block_diagram.png| 1600px]]&lt;br /&gt;
&lt;br /&gt;
Overall &#039;&#039;fejemis_maploc&#039;&#039; data flow from bridge, LiDAR, and RealSense into odometry, RTAB-Map, Nav2, coverage planning, behaviour tree, and robot commands.&lt;br /&gt;
&lt;br /&gt;
== Package Layout ==&lt;br /&gt;
&lt;br /&gt;
Important package folders:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;resources/launch&#039;&#039; - ROS 2 launch files for sensors, odometry, SLAM/localisation, planning, lifecycle managers, and behaviour trees.&lt;br /&gt;
* &#039;&#039;resources/config&#039;&#039; - YAML configuration for odometry, Nav2, RTAB-Map, RealSense, lifecycle managers, and BT plugins.&lt;br /&gt;
* &#039;&#039;resources/behavior&#039;&#039; - behaviour tree XML files. &#039;&#039;main.xml&#039;&#039; is the older BT.CPP format and &#039;&#039;main4.xml&#039;&#039; is the BT.CPP v4 version used on newer ROS distributions.&lt;br /&gt;
* &#039;&#039;scripts&#039;&#039; - Python helper nodes for mission triggering, RViz goal relays, brush services, velocity conversion, and coverage tests.&lt;br /&gt;
* &#039;&#039;src/bt_plugins&#039;&#039; - C++ behaviour tree plugin used by the cleaning behaviour.&lt;br /&gt;
&lt;br /&gt;
== Main Topics and Frames ==&lt;br /&gt;
&lt;br /&gt;
Common frame names:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;map&#039;&#039; - global map frame produced by RTAB-Map / Nav2.&lt;br /&gt;
* &#039;&#039;odom&#039;&#039; - local odometry frame.&lt;br /&gt;
* &#039;&#039;base_link&#039;&#039; - robot base frame.&lt;br /&gt;
* &#039;&#039;lidar_link&#039;&#039; - LiDAR frame.&lt;br /&gt;
* &#039;&#039;camera_camera_link&#039;&#039; and RealSense optical frames - camera frames used by the RealSense driver and RTAB-Map.&lt;br /&gt;
&lt;br /&gt;
Important topics and actions:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;/fejemis/odom&#039;&#039; - wheel odometry from the bridge.&lt;br /&gt;
* &#039;&#039;/fejemis/imu&#039;&#039; and &#039;&#039;/fejemis/mag&#039;&#039; - raw IMU and magnetometer inputs.&lt;br /&gt;
* &#039;&#039;/loc/imu&#039;&#039; - filtered IMU output from Madgwick.&lt;br /&gt;
* &#039;&#039;/loc/odom&#039;&#039; - fused odometry output from the EKF.&lt;br /&gt;
* &#039;&#039;/fejemis/scan&#039;&#039; - LiDAR scan.&lt;br /&gt;
* &#039;&#039;/camera/color/image_raw&#039;&#039;, &#039;&#039;/camera/depth/image_rect_raw&#039;&#039;, and camera info topics - RealSense RGB-D input.&lt;br /&gt;
* &#039;&#039;/camera_scan&#039;&#039; - depth-image-to-laserscan output used as an extra obstacle source.&lt;br /&gt;
* &#039;&#039;/loc/map&#039;&#039; - occupancy map used by Nav2.&lt;br /&gt;
* &#039;&#039;/control/nav2/cmd_vel&#039;&#039; - Nav2 velocity command before Fejemis-specific conversion.&lt;br /&gt;
* &#039;&#039;/control/joy/cmd_vel&#039;&#039; - velocity command sent into the existing Fejemis control path.&lt;br /&gt;
* &#039;&#039;/behavior/set_mission&#039;&#039; - mission command topic consumed by the behaviour tree.&lt;br /&gt;
* &#039;&#039;/blackboard/...&#039;&#039; - blackboard values mirrored into the behaviour tree.&lt;br /&gt;
* &#039;&#039;/fejemis/navigate_to_pose&#039;&#039; - Nav2 action used for waypoint goals.&lt;br /&gt;
* &#039;&#039;/fejemis/navigate_complete_coverage&#039;&#039; - OpenNav coverage action used for coverage missions.&lt;br /&gt;
* &#039;&#039;/control/brush/controller/on&#039;&#039; and &#039;&#039;/control/brush/controller/off&#039;&#039; - brush control trigger services.&lt;br /&gt;
&lt;br /&gt;
== Launch Files ==&lt;br /&gt;
&lt;br /&gt;
The package is designed so the top-level &#039;&#039;fejemis/launch/robot.launch.py&#039;&#039; can include only the parts needed on each Raspberry Pi. The main Pi typically handles bridge control and odometry, while the auxiliary Pi handles heavier sensing such as LiDAR, RealSense, RTAB-Map, and vision.&lt;br /&gt;
&lt;br /&gt;
=== launch/lidar.launch.py ===&lt;br /&gt;
&lt;br /&gt;
Starts the LD19 LiDAR driver from &#039;&#039;ldlidar_stl_ros2&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
Launch arguments:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;ld_make_container&#039;&#039; - whether the LiDAR launch should create a component container. Default: &#039;&#039;True&#039;&#039;.&lt;br /&gt;
* &#039;&#039;ld_container&#039;&#039; - component container name. Default: &#039;&#039;_laser&#039;&#039;.&lt;br /&gt;
* &#039;&#039;ld_container_ns&#039;&#039; - namespace for the component container. Default: &#039;&#039;loc&#039;&#039;.&lt;br /&gt;
* &#039;&#039;port_name&#039;&#039; - serial port for the LiDAR. Default: &#039;&#039;/dev/ttyUSB0&#039;&#039;.&lt;br /&gt;
* &#039;&#039;namespace&#039;&#039; - launch argument declared for compatibility. The included launch currently receives &#039;&#039;fejemis&#039;&#039; as namespace.&lt;br /&gt;
&lt;br /&gt;
Important configuration passed to the driver:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;laser_frame=lidar_link&#039;&#039;&lt;br /&gt;
* &#039;&#039;clockwise_dir=false&#039;&#039;&lt;br /&gt;
* &#039;&#039;range_min=0.2&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The resulting scan is used by RTAB-Map and by the Nav2 costmaps as the main 2D obstacle source.&lt;br /&gt;
&lt;br /&gt;
=== launch/tf.launch.py ===&lt;br /&gt;
&lt;br /&gt;
Publishes static transforms for the physical sensor layout.&lt;br /&gt;
&lt;br /&gt;
Published transforms:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;base_link -&amp;gt; camera_camera_link&#039;&#039; at approximately x=-0.24 m, y=0.0 m, z=0.44 m.&lt;br /&gt;
* On the real robot, &#039;&#039;base_link -&amp;gt; lidar_link&#039;&#039; at approximately x=-0.29 m, y=0.0 m, z=0.25 m, yaw=-1.5708 rad. The yaw correction compensates for the LiDAR&#039;s physical mounting angle.&lt;br /&gt;
* In simulation, &#039;&#039;laser_frame -&amp;gt; lidar_link&#039;&#039; is published instead, because Gazebo already provides &#039;&#039;laser_frame&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
The launch file uses the &#039;&#039;in_sim&#039;&#039; launch configuration to select the real or simulated LiDAR transform.&lt;br /&gt;
&lt;br /&gt;
=== launch/odometry.launch.py ===&lt;br /&gt;
&lt;br /&gt;
Starts the odometry estimation path. On the real robot it runs both the Madgwick IMU filter and the robot_localization EKF. In simulation it only runs the EKF with the simulation-specific configuration.&lt;br /&gt;
&lt;br /&gt;
Launch argument:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;use_sim_time&#039;&#039; - selects real or simulated odometry configuration. Default: &#039;&#039;False&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
Real robot nodes:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;imu_filter_madgwick_node&#039;&#039; named &#039;&#039;imu_filter&#039;&#039; in namespace &#039;&#039;loc&#039;&#039;.&lt;br /&gt;
* &#039;&#039;robot_localization/ekf_node&#039;&#039; named &#039;&#039;ekf&#039;&#039; in namespace &#039;&#039;loc&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
Real robot remappings:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;imu/mag -&amp;gt; /fejemis/mag&#039;&#039;&lt;br /&gt;
* &#039;&#039;imu/data_raw -&amp;gt; /fejemis/imu&#039;&#039;&lt;br /&gt;
* &#039;&#039;imu/data -&amp;gt; imu&#039;&#039;, which resolves to &#039;&#039;/loc/imu&#039;&#039;.&lt;br /&gt;
* &#039;&#039;/loc/odom_in -&amp;gt; /fejemis/odom&#039;&#039;&lt;br /&gt;
* &#039;&#039;odometry/filtered -&amp;gt; odom&#039;&#039;, which resolves to &#039;&#039;/loc/odom&#039;&#039;.&lt;br /&gt;
* diagnostics are remapped to &#039;&#039;/loc/ekf/_diagnostics&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
The EKF configuration in &#039;&#039;config/odometry.yaml&#039;&#039; runs at 30 Hz, uses two-dimensional mode, publishes TF, and fuses:&lt;br /&gt;
&lt;br /&gt;
* Wheel odometry from &#039;&#039;/fejemis/odom&#039;&#039;.&lt;br /&gt;
* RTAB-Map ICP odometry from &#039;&#039;/loc/rtabmap_odom&#039;&#039;.&lt;br /&gt;
* Filtered IMU input is present as &#039;&#039;/loc/imu&#039;&#039;, although the current real configuration disables the IMU state variables in the EKF and relies more heavily on odometry sources for motion estimation.&lt;br /&gt;
&lt;br /&gt;
Simulation mode uses &#039;&#039;config/odometry_sim.yaml&#039;&#039; and avoids the Madgwick IMU orientation because the simulated IMU orientation can conflict with Gazebo odometry frame conventions.&lt;br /&gt;
&lt;br /&gt;
[[File:fejemis_maploc_ekf_block_diagram.png| 800px]]&lt;br /&gt;
&lt;br /&gt;
EKF input/output diagram: &#039;&#039;/fejemis/odom&#039;&#039;, &#039;&#039;/loc/rtabmap_odom&#039;&#039;, &#039;&#039;/loc/imu&#039;&#039; into &#039;&#039;/loc/ekf&#039;&#039;, output &#039;&#039;/loc/odom&#039;&#039; and TF.&lt;br /&gt;
&lt;br /&gt;
=== launch/realsense.launch.py ===&lt;br /&gt;
&lt;br /&gt;
Includes the official &#039;&#039;realsense2_camera&#039;&#039; launch file with the package&#039;s &#039;&#039;config/realsense.yaml&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
Launch arguments:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;camera_namespace&#039;&#039; - namespace for the camera. Default: empty string.&lt;br /&gt;
* &#039;&#039;use_sim_time&#039;&#039; - declared for consistency, although the included launch is mainly configured through the RealSense launch arguments.&lt;br /&gt;
&lt;br /&gt;
Important RealSense settings:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;publish_tf=true&#039;&#039;&lt;br /&gt;
* &#039;&#039;enable_sync=true&#039;&#039;&lt;br /&gt;
* &#039;&#039;unite_imu_method=2&#039;&#039;&lt;br /&gt;
* &#039;&#039;config_file=fejemis_maploc/config/realsense.yaml&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The camera publishes the RGB, depth, camera info, IMU, and TF data used by RTAB-Map and by the depth-to-scan obstacle conversion.&lt;br /&gt;
&lt;br /&gt;
=== launch/rtabmap_slam.launch.py ===&lt;br /&gt;
&lt;br /&gt;
Starts the RTAB-Map mapping/localisation pipeline and includes &#039;&#039;tf.launch.py&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
Launch arguments:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;use_sim_time&#039;&#039; - selects real or simulation config. Default: &#039;&#039;false&#039;&#039;.&lt;br /&gt;
* &#039;&#039;localization&#039;&#039; - if &#039;&#039;true&#039;&#039;, RTAB-Map loads an existing database and does not increment the map. If &#039;&#039;false&#039;&#039;, it starts in mapping mode and deletes the database on start. Default: &#039;&#039;false&#039;&#039;.&lt;br /&gt;
* &#039;&#039;namespace&#039;&#039; - namespace for RTAB-Map. Default: &#039;&#039;loc&#039;&#039;.&lt;br /&gt;
* &#039;&#039;map_location&#039;&#039; - selects the database path. &#039;&#039;lab&#039;&#039; maps to &#039;&#039;~/.ros/rtabmap.db&#039;&#039; and &#039;&#039;asta&#039;&#039; maps to &#039;&#039;~/.ros/rtabmap_asta.db&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
Sensor remappings:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;rgb/image -&amp;gt; /camera/color/image_raw&#039;&#039;&lt;br /&gt;
* &#039;&#039;rgb/camera_info -&amp;gt; /camera/color/camera_info&#039;&#039;&lt;br /&gt;
* &#039;&#039;depth/image -&amp;gt; /camera/depth/image_rect_raw&#039;&#039;&lt;br /&gt;
* &#039;&#039;depth/camera_info -&amp;gt; /camera/depth/camera_info&#039;&#039;&lt;br /&gt;
* &#039;&#039;imu -&amp;gt; /loc/imu&#039;&#039;&lt;br /&gt;
* &#039;&#039;scan -&amp;gt; /fejemis/scan&#039;&#039;&lt;br /&gt;
* &#039;&#039;initialpose -&amp;gt; /initialpose&#039;&#039;&lt;br /&gt;
* &#039;&#039;grid_prob_map -&amp;gt; /map&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Nodes:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;depthimage_to_laserscan_node&#039;&#039; converts the depth image into &#039;&#039;/camera_scan&#039;&#039;.&lt;br /&gt;
* &#039;&#039;rtabmap_sync/rgbd_sync&#039;&#039; synchronises RGB-D data on the real robot.&lt;br /&gt;
* &#039;&#039;rtabmap_odom/icp_odometry&#039;&#039; publishes ICP odometry to &#039;&#039;/loc/rtabmap_odom&#039;&#039; on the real robot.&lt;br /&gt;
* &#039;&#039;rtabmap_slam/rtabmap&#039;&#039; runs either SLAM mode or localisation mode.&lt;br /&gt;
&lt;br /&gt;
In SLAM mode, &#039;&#039;delete_db_on_start=True&#039;&#039; and RTAB-Map is launched with &#039;&#039;-d&#039;&#039;, so the selected database is overwritten. In localisation mode, &#039;&#039;Mem/IncrementalMemory=False&#039;&#039; and &#039;&#039;Mem/InitWMWithAllNodes=True&#039;&#039;, so the existing map is loaded for localisation.&lt;br /&gt;
&lt;br /&gt;
=== launch/map.launch.py ===&lt;br /&gt;
&lt;br /&gt;
Starts a component-based Nav2 map server in namespace &#039;&#039;loc&#039;&#039;, using &#039;&#039;config/nav2_servers.yaml&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
Launch arguments:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;use_sim_time&#039;&#039; - forwarded to the map server.&lt;br /&gt;
* &#039;&#039;map_make_container&#039;&#039;, &#039;&#039;map_container&#039;&#039;, and &#039;&#039;map_container_ns&#039;&#039; - declared as container-related options. The current launch creates a container named &#039;&#039;_map&#039;&#039; with prefix/namespace &#039;&#039;map&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
The map server is configured with:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;topic_name=/loc/map&#039;&#039;&lt;br /&gt;
* &#039;&#039;frame_id=map&#039;&#039;&lt;br /&gt;
* &#039;&#039;yaml_filename=&amp;quot;&amp;quot;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
In the current system, the map is primarily produced by RTAB-Map, while the Nav2 map server exposes map-server functionality for the navigation stack.&lt;br /&gt;
&lt;br /&gt;
=== launch/planning.launch.py ===&lt;br /&gt;
&lt;br /&gt;
Starts the Nav2 planning and navigation servers, the Fejemis velocity relay, the brush service, and optionally the OpenNav coverage server.&lt;br /&gt;
&lt;br /&gt;
Launch arguments:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;namespace&#039;&#039; - namespace for Nav2 nodes. Default: &#039;&#039;fejemis&#039;&#039;.&lt;br /&gt;
* &#039;&#039;use_sim_time&#039;&#039; - forwarded to nodes. Default: &#039;&#039;False&#039;&#039;.&lt;br /&gt;
* &#039;&#039;enable_coverage&#039;&#039; - enables &#039;&#039;opennav_coverage&#039;&#039;. The default is detected from whether the coverage packages are installed.&lt;br /&gt;
&lt;br /&gt;
Nodes:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;nav2_controller/controller_server&#039;&#039; named &#039;&#039;controller&#039;&#039;.&lt;br /&gt;
* &#039;&#039;fejemis_maploc/nav2_cmd_vel_deg_relay.py&#039;&#039;.&lt;br /&gt;
* &#039;&#039;nav2_behaviors/behavior_server&#039;&#039; named &#039;&#039;behavior&#039;&#039;.&lt;br /&gt;
* &#039;&#039;nav2_planner/planner_server&#039;&#039; named &#039;&#039;planner_server&#039;&#039;.&lt;br /&gt;
* &#039;&#039;nav2_bt_navigator/bt_navigator&#039;&#039; named &#039;&#039;bt_navigator&#039;&#039;.&lt;br /&gt;
* &#039;&#039;nav2_map_server/map_server&#039;&#039;.&lt;br /&gt;
* &#039;&#039;fejemis_maploc/brush_control_service.py&#039;&#039; in namespace &#039;&#039;control/brush&#039;&#039;, named &#039;&#039;controller&#039;&#039;.&lt;br /&gt;
* &#039;&#039;opennav_coverage/opennav_coverage&#039;&#039; named &#039;&#039;coverage_server&#039;&#039;, when enabled.&lt;br /&gt;
&lt;br /&gt;
Important remappings:&lt;br /&gt;
&lt;br /&gt;
* Nav2 odometry is remapped from &#039;&#039;/behavior/odom&#039;&#039; to &#039;&#039;/loc/odom&#039;&#039;.&lt;br /&gt;
* Nav2 map input is remapped from &#039;&#039;/behavior/map&#039;&#039; to &#039;&#039;/loc/map&#039;&#039;.&lt;br /&gt;
* Nav2 controller output &#039;&#039;cmd_vel&#039;&#039; is remapped to &#039;&#039;/control/nav2/cmd_vel&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
The velocity relay then converts angular velocity units and republishes to &#039;&#039;/control/joy/cmd_vel&#039;&#039; for the existing Fejemis control path.&lt;br /&gt;
&lt;br /&gt;
The Nav2 configuration in &#039;&#039;config/nav2_servers.yaml&#039;&#039; uses:&lt;br /&gt;
&lt;br /&gt;
* Regulated Pure Pursuit as the path follower.&lt;br /&gt;
* Navfn as the global planner.&lt;br /&gt;
* Local costmap sources from LiDAR, camera scan, net scan, and cable scan.&lt;br /&gt;
* Global costmap with static map, LiDAR obstacle layer, and inflation layer.&lt;br /&gt;
* The OpenNav coverage navigator when coverage support is enabled.&lt;br /&gt;
&lt;br /&gt;
=== launch/lifecycle.launch.py ===&lt;br /&gt;
&lt;br /&gt;
Starts Nav2 lifecycle managers.&lt;br /&gt;
&lt;br /&gt;
Launch arguments:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;in_sim&#039;&#039; - forwarded as &#039;&#039;use_sim_time&#039;&#039;.&lt;br /&gt;
* &#039;&#039;enable_loc_lfc&#039;&#039; - controls whether the localisation lifecycle manager is started. Default: &#039;&#039;false&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
Lifecycle managers:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;/fejemis/beh_lfc&#039;&#039; - behaviour/navigation lifecycle manager.&lt;br /&gt;
* &#039;&#039;/loc/loc_lfc&#039;&#039; - localisation lifecycle manager, only when &#039;&#039;enable_loc_lfc=true&#039;&#039;.&lt;br /&gt;
* &#039;&#039;/fejemis/hard_lfc&#039;&#039; - hardware lifecycle manager, disabled in simulation.&lt;br /&gt;
&lt;br /&gt;
Diagnostics are remapped to &#039;&#039;/diag/beh_lfc&#039;&#039;, &#039;&#039;/diag/loc_lfc&#039;&#039;, and &#039;&#039;/diag/hard_lfc&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
=== launch/lifecycle_managers_only.launch.py ===&lt;br /&gt;
&lt;br /&gt;
Starts the navigation-side launch files without the heavy sensor components. This is useful when another machine is responsible for LiDAR, RealSense, or RTAB-Map.&lt;br /&gt;
&lt;br /&gt;
Launch arguments:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;in_sim&#039;&#039; - enables simulation time.&lt;br /&gt;
* &#039;&#039;localization&#039;&#039; - when true, starts the planning stack on the main Pi. Default: &#039;&#039;false&#039;&#039;.&lt;br /&gt;
* &#039;&#039;enable_coverage&#039;&#039; - enables coverage navigation. Default: &#039;&#039;true&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
Included launch files:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;planning.launch.py&#039;&#039; is included only when &#039;&#039;in_sim=true&#039;&#039; or &#039;&#039;localization=true&#039;&#039;.&lt;br /&gt;
* &#039;&#039;lifecycle.launch.py&#039;&#039; is included immediately.&lt;br /&gt;
* &#039;&#039;behavior.launch.py&#039;&#039; is included after a 30 second delay, giving Nav2 servers time to become active before the BT engine connects to action servers.&lt;br /&gt;
&lt;br /&gt;
=== launch/behavior.launch.py ===&lt;br /&gt;
&lt;br /&gt;
Starts the Fejemis behaviour tree engine from &#039;&#039;raubase_core&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
Launch argument:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;use_sim_time&#039;&#039; - forwarded to the BT engine.&lt;br /&gt;
&lt;br /&gt;
The launch file selects the behaviour tree XML based on the ROS distribution:&lt;br /&gt;
&lt;br /&gt;
* Older BehaviourTree.CPP format: &#039;&#039;resources/behavior/main.xml&#039;&#039;.&lt;br /&gt;
* BehaviourTree.CPP v4 format: &#039;&#039;resources/behavior/main4.xml&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
The node launched is:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;raubase_core/bt_engine&#039;&#039;, configured by &#039;&#039;config/bt_engine.yaml&#039;&#039; in namespace &#039;&#039;fejemis&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
The BT plugin configuration loads:&lt;br /&gt;
&lt;br /&gt;
* raubase logging, mission, blackboard, coverage, and mixer plugins.&lt;br /&gt;
* Nav2 action plugins for single trigger, follow path, drive on heading, spin, and navigate to pose.&lt;br /&gt;
* OpenNav coverage path computation plugin.&lt;br /&gt;
* The package&#039;s custom &#039;&#039;fejemis_call_trigger_service_bt_node&#039;&#039; plugin.&lt;br /&gt;
&lt;br /&gt;
=== launch/robot.launch.py inclusion ===&lt;br /&gt;
&lt;br /&gt;
The top-level &#039;&#039;fejemis&#039;&#039; package includes &#039;&#039;fejemis_maploc&#039;&#039; from &#039;&#039;fejemis/launch/robot.launch.py&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
Typical distribution:&lt;br /&gt;
&lt;br /&gt;
* Main Pi: bridge control, odometry, lifecycle managers, behaviour tree, and planning when localisation is enabled.&lt;br /&gt;
* Auxiliary Pi: LiDAR, RTAB-Map, RealSense, and optional vision.&lt;br /&gt;
&lt;br /&gt;
The top-level launch waits 3 seconds after bridge startup before launching the rest of the stack.&lt;br /&gt;
&lt;br /&gt;
== Script Nodes ==&lt;br /&gt;
&lt;br /&gt;
=== nav2_cmd_vel_deg_relay.py ===&lt;br /&gt;
&lt;br /&gt;
Purpose: adapts Nav2 velocity commands to the Fejemis control convention.&lt;br /&gt;
&lt;br /&gt;
Subscriptions:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;/control/nav2/cmd_vel&#039;&#039; - &#039;&#039;geometry_msgs/Twist&#039;&#039;, with angular velocity in rad/s.&lt;br /&gt;
&lt;br /&gt;
Publications:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;/control/joy/cmd_vel&#039;&#039; - &#039;&#039;geometry_msgs/Twist&#039;&#039;, with angular velocity converted to deg/s.&lt;br /&gt;
&lt;br /&gt;
The linear fields are copied unchanged. The angular x, y, and z fields are multiplied by 180/pi. This is needed because Nav2 uses the normal ROS convention of rad/s, while the existing Fejemis bridge control path expects angular velocity in deg/s.&lt;br /&gt;
&lt;br /&gt;
=== brush_control_service.py ===&lt;br /&gt;
&lt;br /&gt;
Purpose: exposes simple ROS services for turning the brush on and off.&lt;br /&gt;
&lt;br /&gt;
Parameters:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;manage_topic&#039;&#039; - topic for &#039;&#039;fejemis_bridge/msg/SteerManage&#039;&#039;. Default: &#039;&#039;/fejemis/front/manage&#039;&#039;.&lt;br /&gt;
* &#039;&#039;qos_depth&#039;&#039; - publisher QoS depth. Default: &#039;&#039;10&#039;&#039;.&lt;br /&gt;
* &#039;&#039;initial_brush_on&#039;&#039; - initial internal state. Default: &#039;&#039;False&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
Publications:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;manage_topic&#039;&#039; - publishes &#039;&#039;SteerManage.BRUSH_ON&#039;&#039; or &#039;&#039;SteerManage.BRUSH_OFF&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
Services:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;~/set&#039;&#039; - &#039;&#039;std_srvs/SetBool&#039;&#039;, true means brush on and false means brush off.&lt;br /&gt;
* &#039;&#039;~/on&#039;&#039; - &#039;&#039;std_srvs/Trigger&#039;&#039;, turns the brush on.&lt;br /&gt;
* &#039;&#039;~/off&#039;&#039; - &#039;&#039;std_srvs/Trigger&#039;&#039;, turns the brush off.&lt;br /&gt;
&lt;br /&gt;
In &#039;&#039;planning.launch.py&#039;&#039; this node runs as &#039;&#039;/control/brush/controller&#039;&#039;, which creates the services used by the behaviour tree:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;/control/brush/controller/on&#039;&#039;&lt;br /&gt;
* &#039;&#039;/control/brush/controller/off&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
=== rviz_goal_relay.py ===&lt;br /&gt;
&lt;br /&gt;
Purpose: lets an operator send a Nav2 goal from RViz.&lt;br /&gt;
&lt;br /&gt;
Subscriptions:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;/goal_pose&#039;&#039; - RViz 2D Nav Goal as &#039;&#039;geometry_msgs/PoseStamped&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
Action clients:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;/fejemis/navigate_to_pose&#039;&#039; - &#039;&#039;nav2_msgs/action/NavigateToPose&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
When a goal is clicked in RViz, the node checks that the action server is ready, forwards the goal, and logs whether it was accepted and whether it succeeded.&lt;br /&gt;
&lt;br /&gt;
=== mission_trigger.py ===&lt;br /&gt;
&lt;br /&gt;
Purpose: starts a cleaning mission by publishing blackboard values and then setting the active mission.&lt;br /&gt;
&lt;br /&gt;
Parameters:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;mission&#039;&#039; - mission string to publish. Default: &#039;&#039;cleaning/waypoint&#039;&#039;.&lt;br /&gt;
* &#039;&#039;dock_pose&#039;&#039; - dock pose as &#039;&#039;[x, y, yaw]&#039;&#039;. Default: &#039;&#039;[2.62, 3.9, 0.0]&#039;&#039;.&lt;br /&gt;
* &#039;&#039;waypoint_pose&#039;&#039; - waypoint pose as &#039;&#039;[x, y, yaw]&#039;&#039;. Default: &#039;&#039;[2.10491, 0.497438, 3.11127]&#039;&#039;.&lt;br /&gt;
* &#039;&#039;clean_poly&#039;&#039; - polygon string used for cleaning area blackboard values.&lt;br /&gt;
* &#039;&#039;startup_delay&#039;&#039; - delay before publishing. Default: &#039;&#039;2.0&#039;&#039; seconds.&lt;br /&gt;
&lt;br /&gt;
Publications:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;/blackboard/dock_x&#039;&#039;, &#039;&#039;/blackboard/dock_y&#039;&#039;, &#039;&#039;/blackboard/dock_yaw&#039;&#039;&lt;br /&gt;
* &#039;&#039;/blackboard/waypoint_x&#039;&#039;, &#039;&#039;/blackboard/waypoint_y&#039;&#039;, &#039;&#039;/blackboard/waypoint_yaw&#039;&#039;&lt;br /&gt;
* &#039;&#039;/blackboard/field_polygon&#039;&#039;&lt;br /&gt;
* &#039;&#039;/blackboard/clean_poly&#039;&#039;&lt;br /&gt;
* &#039;&#039;/blackboard/start_pose&#039;&#039;&lt;br /&gt;
* &#039;&#039;/blackboard/dock_pose&#039;&#039;&lt;br /&gt;
* &#039;&#039;/blackboard/waypoint_pose&#039;&#039;&lt;br /&gt;
* &#039;&#039;/behavior/set_mission&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The blackboard publishers use transient-local QoS so late subscribers can still receive the latest values. Pose strings are encoded as:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
stamp;frame;x;y;z;qx;qy;qz;qw&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The node also subscribes to &#039;&#039;/behavior/set_mission&#039;&#039;. When it sees &#039;&#039;auto/idle&#039;&#039; after the mission has run, it logs completion and shuts down.&lt;br /&gt;
&lt;br /&gt;
=== complete_coverage_planner.py ===&lt;br /&gt;
&lt;br /&gt;
Purpose: interactive coverage test node for RViz.&lt;br /&gt;
&lt;br /&gt;
Parameters:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;rviz_click_topic&#039;&#039; - clicked point topic. Default: &#039;&#039;/clicked_point&#039;&#039;.&lt;br /&gt;
* &#039;&#039;coverage_frame&#039;&#039; - frame for the coverage polygon. Default: &#039;&#039;map&#039;&#039;.&lt;br /&gt;
* &#039;&#039;min_polygon_points&#039;&#039; - number of clicked points before sending a goal. Default: &#039;&#039;4&#039;&#039;.&lt;br /&gt;
* &#039;&#039;auto_close_polygon&#039;&#039; - whether to append the first point as the final point. Default: &#039;&#039;True&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
Subscriptions:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;/clicked_point&#039;&#039; by default - RViz point clicks.&lt;br /&gt;
&lt;br /&gt;
Action clients:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;fejemis/navigate_complete_coverage&#039;&#039; - &#039;&#039;opennav_coverage_msgs/action/NavigateCompleteCoverage&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
The node waits for &#039;&#039;fejemis/bt_navigator&#039;&#039; to become active, collects clicked points in RViz, converts them to a &#039;&#039;geometry_msgs/Polygon&#039;&#039;, and sends a coverage navigation action once enough points have been collected.&lt;br /&gt;
&lt;br /&gt;
=== fixed_complete_coverage_planner.py ===&lt;br /&gt;
&lt;br /&gt;
Purpose: fixed-polygon coverage test node.&lt;br /&gt;
&lt;br /&gt;
Parameters:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;coverage_frame&#039;&#039; - frame for the coverage polygon. Default: &#039;&#039;map&#039;&#039;.&lt;br /&gt;
* &#039;&#039;min_polygon_points&#039;&#039; - retained from the interactive tester. Default: &#039;&#039;4&#039;&#039;.&lt;br /&gt;
* &#039;&#039;auto_close_polygon&#039;&#039; - whether to append the first point as the final point. Default: &#039;&#039;True&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
Action clients:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;fejemis/navigate_complete_coverage&#039;&#039; - &#039;&#039;opennav_coverage_msgs/action/NavigateCompleteCoverage&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
The node waits for &#039;&#039;fejemis/bt_navigator&#039;&#039; to become active, then sends a fixed four-point polygon:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
(-5.35,  2.45)&lt;br /&gt;
( 2.83,  3.18)&lt;br /&gt;
( 3.22, -5.08)&lt;br /&gt;
(-4.76, -5.97)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This is useful for testing coverage planning without clicking points manually in RViz.&lt;br /&gt;
&lt;br /&gt;
== C++ Behaviour Tree Plugin ==&lt;br /&gt;
&lt;br /&gt;
=== fejemis_call_trigger_service_bt_node ===&lt;br /&gt;
&lt;br /&gt;
The C++ plugin &#039;&#039;src/bt_plugins/call_trigger_service_bt_node.cpp&#039;&#039; registers the behaviour tree node &#039;&#039;CallTriggerService&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
Inputs:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;service_name&#039;&#039; - service to call.&lt;br /&gt;
* &#039;&#039;wait_for_service_ms&#039;&#039; - timeout while waiting for service availability. Default: &#039;&#039;1000&#039;&#039;.&lt;br /&gt;
* &#039;&#039;call_timeout_ms&#039;&#039; - timeout for the service call. Default: &#039;&#039;2000&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
The node creates a &#039;&#039;std_srvs/Trigger&#039;&#039; client, waits for the target service, calls it, and returns:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;SUCCESS&#039;&#039; when the service exists, responds before timeout, and returns &#039;&#039;success=true&#039;&#039;.&lt;br /&gt;
* &#039;&#039;FAILURE&#039;&#039; when the service is unavailable, the call times out, or the response reports failure.&lt;br /&gt;
&lt;br /&gt;
The cleaning subtree uses it to call:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;/control/brush/controller/on&#039;&#039;&lt;br /&gt;
* &#039;&#039;/control/brush/controller/off&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
== Behaviour Tree ==&lt;br /&gt;
&lt;br /&gt;
The active behaviour tree on newer ROS distributions is &#039;&#039;resources/behavior/main4.xml&#039;&#039;. It is loaded by &#039;&#039;behavior.launch.py&#039;&#039; through the &#039;&#039;raubase_core/bt_engine&#039;&#039; node.&lt;br /&gt;
&lt;br /&gt;
=== Main Loop ===&lt;br /&gt;
&lt;br /&gt;
The root tree is &#039;&#039;main&#039;&#039;:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
main&lt;br /&gt;
`-- Sequence&lt;br /&gt;
    |-- SingleTrigger&lt;br /&gt;
    |   `-- t_init&lt;br /&gt;
    `-- KeepRunningUntilFailure&lt;br /&gt;
        `-- ReactiveSequence&lt;br /&gt;
            |-- ForceSuccess&lt;br /&gt;
            |   `-- t_passive&lt;br /&gt;
            `-- t_runtime&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The structure means:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;t_init&#039;&#039; runs once at startup.&lt;br /&gt;
* &#039;&#039;t_passive&#039;&#039; runs repeatedly and updates blackboard/mission state.&lt;br /&gt;
* &#039;&#039;t_runtime&#039;&#039; selects the active behaviour based on the current mission.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Visualization placeholder:&#039;&#039;&#039; exported Groot image of &#039;&#039;main4.xml&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
=== t_init ===&lt;br /&gt;
&lt;br /&gt;
Initialises the robot behaviour state:&lt;br /&gt;
&lt;br /&gt;
* Sets mission to &#039;&#039;auto/idle&#039;&#039;.&lt;br /&gt;
* Sets blackboard key &#039;&#039;initialized=1&#039;&#039;.&lt;br /&gt;
* Sets calibration square distance &#039;&#039;calib_sq_dist=7&#039;&#039;.&lt;br /&gt;
* Opens the mixer port using &#039;&#039;OpenMixerPort&#039;&#039; with priority 4 and topic &#039;&#039;/behavior&#039;&#039;.&lt;br /&gt;
* Logs that the robot is initialized.&lt;br /&gt;
&lt;br /&gt;
=== t_passive ===&lt;br /&gt;
&lt;br /&gt;
Runs as the passive safety/update loop:&lt;br /&gt;
&lt;br /&gt;
* Logs &#039;&#039;Safety loop&#039;&#039; at debug level.&lt;br /&gt;
* Refreshes &#039;&#039;start_pose&#039;&#039;, &#039;&#039;waypoint_pose&#039;&#039;, &#039;&#039;clean_poly&#039;&#039;, and &#039;&#039;field_polygon&#039;&#039; from &#039;&#039;/blackboard/...&#039;&#039; topics.&lt;br /&gt;
* Refreshes the current mission from &#039;&#039;/behavior/set_mission&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
=== t_runtime ===&lt;br /&gt;
&lt;br /&gt;
Selects behaviour using a reactive fallback:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
t_runtime&lt;br /&gt;
|-- t_idle_runtime&lt;br /&gt;
|-- t_calib&lt;br /&gt;
|-- t_mapping&lt;br /&gt;
|-- t_cleaning&lt;br /&gt;
`-- unknown mission handler -&amp;gt; set idle&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The first matching mission branch runs. If no known branch matches, the tree logs an error and returns to idle.&lt;br /&gt;
&lt;br /&gt;
=== Idle ===&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;t_idle_runtime&#039;&#039; succeeds when the current mission is:&lt;br /&gt;
&lt;br /&gt;
* mission: &#039;&#039;auto&#039;&#039;&lt;br /&gt;
* mission_sub: &#039;&#039;idle&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
This keeps the robot in a waiting state. Sensors, telemetry, and subscriptions remain active, but no movement commands are issued. The robot leaves this state when a new mission is received through &#039;&#039;/behavior/set_mission&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
=== Calibration ===&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;t_calib&#039;&#039; chooses between line calibration and square calibration.&lt;br /&gt;
&lt;br /&gt;
Line calibration, &#039;&#039;t_calib_line&#039;&#039;:&lt;br /&gt;
&lt;br /&gt;
* Requires mission &#039;&#039;calib/line&#039;&#039;.&lt;br /&gt;
* Drives forward using &#039;&#039;DriveOnHeading&#039;&#039; &lt;br /&gt;
* &#039;&#039;calib_sq_dist&#039;&#039; is set to 0.05 m/s during initialization.&lt;br /&gt;
* Returns to idle.&lt;br /&gt;
&lt;br /&gt;
Square calibration, &#039;&#039;t_calib_square&#039;&#039;:&lt;br /&gt;
&lt;br /&gt;
* Requires mission &#039;&#039;calib/*&#039;&#039;.&lt;br /&gt;
* Runs &#039;&#039;f_calib_side&#039;&#039; four times.&lt;br /&gt;
* Each side drives forward and spins 1.57 rad.&lt;br /&gt;
* Returns to idle.&lt;br /&gt;
Each &#039;&#039;f_calib_side&#039;&#039; contains:&lt;br /&gt;
&lt;br /&gt;
*DriveOnHeading&lt;br /&gt;
*Spin&lt;br /&gt;
*A quarter-turn style calibration step&lt;br /&gt;
Calibration succeeds when each action in the sequence completes successfully.&lt;br /&gt;
&lt;br /&gt;
=== Mapping ===&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;t_mapping&#039;&#039; currently checks for &#039;&#039;mission mapping/*&#039;&#039; and then returns to idle.&lt;br /&gt;
&lt;br /&gt;
*Requires mission mapping/*&lt;br /&gt;
&lt;br /&gt;
*Currently acts as a placeholder&lt;br /&gt;
&lt;br /&gt;
*Does not yet launch the SLAM or exploration pipeline&lt;br /&gt;
&lt;br /&gt;
*Returns to idle immediately&lt;br /&gt;
&lt;br /&gt;
This branch should later be expanded to perform autonomous mapping, localization checks, and map saving.&lt;br /&gt;
&lt;br /&gt;
=== Cleaning ===&lt;br /&gt;
&lt;br /&gt;
The current cleaning branch is &#039;&#039;t_cleaning&#039;&#039;:&lt;br /&gt;
&lt;br /&gt;
* Requires mission &#039;&#039;cleaning/waypoint&#039;&#039; with mission_sub &#039;&#039;idle&#039;&#039;.&lt;br /&gt;
* Navigates to &#039;&#039;{waypoint_pose}&#039;&#039; using &#039;&#039;NavigateToPose&#039;&#039;.&lt;br /&gt;
* Sets current mission to &#039;&#039;cleaning/area&#039;&#039;.&lt;br /&gt;
* Returns to idle.&lt;br /&gt;
&lt;br /&gt;
Supporting cleaning subtrees are already present:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;f_get_area_to_clean&#039;&#039; sets a polygon and calls &#039;&#039;ComputeCoveragePath&#039;&#039;.&lt;br /&gt;
* &#039;&#039;f_clean_area&#039;&#039; turns the brush on, follows &#039;&#039;{cover_nav_path}&#039;&#039;, and turns the brush off.&lt;br /&gt;
* &#039;&#039;f_go_to_path_start&#039;&#039; is a placeholder that logs navigation to the path start.&lt;br /&gt;
&lt;br /&gt;
These subtrees show the intended full cleaning sequence, although the active &#039;&#039;t_cleaning&#039;&#039; branch currently only navigates to a waypoint and changes mission state.&lt;br /&gt;
[[File:cleaning.png| 800px]]&lt;br /&gt;
&lt;br /&gt;
Cleanign Node Behaviour Sequence: mission trigger -&amp;gt; waypoint navigation -&amp;gt; coverage path computation -&amp;gt; brush on -&amp;gt; follow coverage path -&amp;gt; brush off -&amp;gt; idle.&lt;br /&gt;
&lt;br /&gt;
== Configuration Files ==&lt;br /&gt;
&lt;br /&gt;
=== config/odometry.yaml and config/odometry_sim.yaml ===&lt;br /&gt;
&lt;br /&gt;
Configure the EKF and IMU filter. The real configuration fuses wheel odometry and RTAB-Map ICP odometry in 2D mode. The simulation configuration is separated to avoid conflicting simulated IMU orientation.&lt;br /&gt;
&lt;br /&gt;
=== config/rtabmap.yaml and config/rtabmap_sim.yaml ===&lt;br /&gt;
&lt;br /&gt;
Configure RTAB-Map for real robot and simulation use. &#039;&#039;rtabmap_slam.launch.py&#039;&#039; selects between them using &#039;&#039;use_sim_time&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
=== config/nav2_servers.yaml and config/nav2_sim.yaml ===&lt;br /&gt;
&lt;br /&gt;
Configure Nav2 controller, planner, behaviour server, costmaps, BT navigator, coverage server, and map server. The main real-robot launch uses &#039;&#039;nav2_servers.yaml&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
=== config/bt_engine.yaml ===&lt;br /&gt;
&lt;br /&gt;
Lists behaviour tree plugins loaded by &#039;&#039;raubase_core/bt_engine&#039;&#039;, including the Fejemis custom trigger-service plugin.&lt;br /&gt;
&lt;br /&gt;
=== config/lifecycle.yaml ===&lt;br /&gt;
&lt;br /&gt;
Configures lifecycle manager node lists and autostart behaviour for the navigation, localisation, and hardware lifecycle managers.&lt;br /&gt;
&lt;br /&gt;
=== config/realsense.yaml ===&lt;br /&gt;
&lt;br /&gt;
RealSense camera configuration loaded by &#039;&#039;realsense.launch.py&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
== Typical Usage ==&lt;br /&gt;
&lt;br /&gt;
Start the complete robot stack through the top-level launch:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ros2 launch fejemis robot.launch.py localization:=true map_location:=asta&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Run on both Raspberry Pis. The launch file decides which parts run on the main and auxiliary computers through the &#039;&#039;is_aux&#039;&#039; argument.&lt;br /&gt;
&lt;br /&gt;
Start a waypoint cleaning mission after the system is up:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ros2 run fejemis_maploc mission_trigger.py&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Send an RViz navigation goal:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ros2 run fejemis_maploc rviz_goal_relay.py&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Test coverage navigation with RViz clicked points:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ros2 run fejemis_maploc complete_coverage_planner.py&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Test coverage navigation with the fixed polygon:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ros2 run fejemis_maploc fixed_complete_coverage_planner.py&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Notes and Future Work ==&lt;br /&gt;
&lt;br /&gt;
* Clarify which Raspberry Pi should run each launch file in the final deployment.&lt;br /&gt;
* Expand the cleaning behaviour section once &#039;&#039;t_cleaning&#039;&#039; is connected to &#039;&#039;f_get_area_to_clean&#039;&#039; and &#039;&#039;f_clean_area&#039;&#039; in the active tree.&lt;br /&gt;
* Test and validate the complete cleaning behaviour structure in the final hardware setup, as it has not yet been fully evaluated due to current Raspberry Pi limitations.&lt;/div&gt;</summary>
		<author><name>S252724</name></author>
	</entry>
	<entry>
		<id>https://rsewiki.electro.dtu.dk/index.php?title=Fejemis_Maploc_Ros2&amp;diff=8970</id>
		<title>Fejemis Maploc Ros2</title>
		<link rel="alternate" type="text/html" href="https://rsewiki.electro.dtu.dk/index.php?title=Fejemis_Maploc_Ros2&amp;diff=8970"/>
		<updated>2026-05-28T10:27:32Z</updated>

		<summary type="html">&lt;p&gt;S252724: /* Cleaning */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Back to [[Fejemis ROS2 Software]]&lt;br /&gt;
&lt;br /&gt;
== Purpose ==&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fejemis_maploc&#039;&#039; is the mapping, localisation, navigation, and behaviour package for Fejemis. It connects the robot&#039;s sensor data to the navigation stack and exposes the high-level behaviour tree used to run calibration, mapping, waypoint navigation, and cleaning missions.&lt;br /&gt;
&lt;br /&gt;
The package sits between the low-level bridge and the high-level mission logic:&lt;br /&gt;
&lt;br /&gt;
* The bridge publishes wheel odometry, IMU data, and accepts velocity / brush commands.&lt;br /&gt;
* &#039;&#039;fejemis_maploc&#039;&#039; filters odometry, starts LiDAR and RealSense sensing, runs RTAB-Map, starts Nav2, and runs the Fejemis behaviour tree.&lt;br /&gt;
* The behaviour tree sends commands to Nav2, the coverage planner, the mixer, and the brush service.&lt;br /&gt;
[[File:maploc_general_block_diagram.png| 1600px]]&lt;br /&gt;
&lt;br /&gt;
Overall &#039;&#039;fejemis_maploc&#039;&#039; data flow from bridge, LiDAR, and RealSense into odometry, RTAB-Map, Nav2, coverage planning, behaviour tree, and robot commands.&lt;br /&gt;
&lt;br /&gt;
== Package Layout ==&lt;br /&gt;
&lt;br /&gt;
Important package folders:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;resources/launch&#039;&#039; - ROS 2 launch files for sensors, odometry, SLAM/localisation, planning, lifecycle managers, and behaviour trees.&lt;br /&gt;
* &#039;&#039;resources/config&#039;&#039; - YAML configuration for odometry, Nav2, RTAB-Map, RealSense, lifecycle managers, and BT plugins.&lt;br /&gt;
* &#039;&#039;resources/behavior&#039;&#039; - behaviour tree XML files. &#039;&#039;main.xml&#039;&#039; is the older BT.CPP format and &#039;&#039;main4.xml&#039;&#039; is the BT.CPP v4 version used on newer ROS distributions.&lt;br /&gt;
* &#039;&#039;scripts&#039;&#039; - Python helper nodes for mission triggering, RViz goal relays, brush services, velocity conversion, and coverage tests.&lt;br /&gt;
* &#039;&#039;src/bt_plugins&#039;&#039; - C++ behaviour tree plugin used by the cleaning behaviour.&lt;br /&gt;
&lt;br /&gt;
== Main Topics and Frames ==&lt;br /&gt;
&lt;br /&gt;
Common frame names:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;map&#039;&#039; - global map frame produced by RTAB-Map / Nav2.&lt;br /&gt;
* &#039;&#039;odom&#039;&#039; - local odometry frame.&lt;br /&gt;
* &#039;&#039;base_link&#039;&#039; - robot base frame.&lt;br /&gt;
* &#039;&#039;lidar_link&#039;&#039; - LiDAR frame.&lt;br /&gt;
* &#039;&#039;camera_camera_link&#039;&#039; and RealSense optical frames - camera frames used by the RealSense driver and RTAB-Map.&lt;br /&gt;
&lt;br /&gt;
Important topics and actions:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;/fejemis/odom&#039;&#039; - wheel odometry from the bridge.&lt;br /&gt;
* &#039;&#039;/fejemis/imu&#039;&#039; and &#039;&#039;/fejemis/mag&#039;&#039; - raw IMU and magnetometer inputs.&lt;br /&gt;
* &#039;&#039;/loc/imu&#039;&#039; - filtered IMU output from Madgwick.&lt;br /&gt;
* &#039;&#039;/loc/odom&#039;&#039; - fused odometry output from the EKF.&lt;br /&gt;
* &#039;&#039;/fejemis/scan&#039;&#039; - LiDAR scan.&lt;br /&gt;
* &#039;&#039;/camera/color/image_raw&#039;&#039;, &#039;&#039;/camera/depth/image_rect_raw&#039;&#039;, and camera info topics - RealSense RGB-D input.&lt;br /&gt;
* &#039;&#039;/camera_scan&#039;&#039; - depth-image-to-laserscan output used as an extra obstacle source.&lt;br /&gt;
* &#039;&#039;/loc/map&#039;&#039; - occupancy map used by Nav2.&lt;br /&gt;
* &#039;&#039;/control/nav2/cmd_vel&#039;&#039; - Nav2 velocity command before Fejemis-specific conversion.&lt;br /&gt;
* &#039;&#039;/control/joy/cmd_vel&#039;&#039; - velocity command sent into the existing Fejemis control path.&lt;br /&gt;
* &#039;&#039;/behavior/set_mission&#039;&#039; - mission command topic consumed by the behaviour tree.&lt;br /&gt;
* &#039;&#039;/blackboard/...&#039;&#039; - blackboard values mirrored into the behaviour tree.&lt;br /&gt;
* &#039;&#039;/fejemis/navigate_to_pose&#039;&#039; - Nav2 action used for waypoint goals.&lt;br /&gt;
* &#039;&#039;/fejemis/navigate_complete_coverage&#039;&#039; - OpenNav coverage action used for coverage missions.&lt;br /&gt;
* &#039;&#039;/control/brush/controller/on&#039;&#039; and &#039;&#039;/control/brush/controller/off&#039;&#039; - brush control trigger services.&lt;br /&gt;
&lt;br /&gt;
== Launch Files ==&lt;br /&gt;
&lt;br /&gt;
The package is designed so the top-level &#039;&#039;fejemis/launch/robot.launch.py&#039;&#039; can include only the parts needed on each Raspberry Pi. The main Pi typically handles bridge control and odometry, while the auxiliary Pi handles heavier sensing such as LiDAR, RealSense, RTAB-Map, and vision.&lt;br /&gt;
&lt;br /&gt;
=== launch/lidar.launch.py ===&lt;br /&gt;
&lt;br /&gt;
Starts the LD19 LiDAR driver from &#039;&#039;ldlidar_stl_ros2&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
Launch arguments:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;ld_make_container&#039;&#039; - whether the LiDAR launch should create a component container. Default: &#039;&#039;True&#039;&#039;.&lt;br /&gt;
* &#039;&#039;ld_container&#039;&#039; - component container name. Default: &#039;&#039;_laser&#039;&#039;.&lt;br /&gt;
* &#039;&#039;ld_container_ns&#039;&#039; - namespace for the component container. Default: &#039;&#039;loc&#039;&#039;.&lt;br /&gt;
* &#039;&#039;port_name&#039;&#039; - serial port for the LiDAR. Default: &#039;&#039;/dev/ttyUSB0&#039;&#039;.&lt;br /&gt;
* &#039;&#039;namespace&#039;&#039; - launch argument declared for compatibility. The included launch currently receives &#039;&#039;fejemis&#039;&#039; as namespace.&lt;br /&gt;
&lt;br /&gt;
Important configuration passed to the driver:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;laser_frame=lidar_link&#039;&#039;&lt;br /&gt;
* &#039;&#039;clockwise_dir=false&#039;&#039;&lt;br /&gt;
* &#039;&#039;range_min=0.2&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The resulting scan is used by RTAB-Map and by the Nav2 costmaps as the main 2D obstacle source.&lt;br /&gt;
&lt;br /&gt;
=== launch/tf.launch.py ===&lt;br /&gt;
&lt;br /&gt;
Publishes static transforms for the physical sensor layout.&lt;br /&gt;
&lt;br /&gt;
Published transforms:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;base_link -&amp;gt; camera_camera_link&#039;&#039; at approximately x=-0.24 m, y=0.0 m, z=0.44 m.&lt;br /&gt;
* On the real robot, &#039;&#039;base_link -&amp;gt; lidar_link&#039;&#039; at approximately x=-0.29 m, y=0.0 m, z=0.25 m, yaw=-1.5708 rad. The yaw correction compensates for the LiDAR&#039;s physical mounting angle.&lt;br /&gt;
* In simulation, &#039;&#039;laser_frame -&amp;gt; lidar_link&#039;&#039; is published instead, because Gazebo already provides &#039;&#039;laser_frame&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
The launch file uses the &#039;&#039;in_sim&#039;&#039; launch configuration to select the real or simulated LiDAR transform.&lt;br /&gt;
&lt;br /&gt;
=== launch/odometry.launch.py ===&lt;br /&gt;
&lt;br /&gt;
Starts the odometry estimation path. On the real robot it runs both the Madgwick IMU filter and the robot_localization EKF. In simulation it only runs the EKF with the simulation-specific configuration.&lt;br /&gt;
&lt;br /&gt;
Launch argument:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;use_sim_time&#039;&#039; - selects real or simulated odometry configuration. Default: &#039;&#039;False&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
Real robot nodes:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;imu_filter_madgwick_node&#039;&#039; named &#039;&#039;imu_filter&#039;&#039; in namespace &#039;&#039;loc&#039;&#039;.&lt;br /&gt;
* &#039;&#039;robot_localization/ekf_node&#039;&#039; named &#039;&#039;ekf&#039;&#039; in namespace &#039;&#039;loc&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
Real robot remappings:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;imu/mag -&amp;gt; /fejemis/mag&#039;&#039;&lt;br /&gt;
* &#039;&#039;imu/data_raw -&amp;gt; /fejemis/imu&#039;&#039;&lt;br /&gt;
* &#039;&#039;imu/data -&amp;gt; imu&#039;&#039;, which resolves to &#039;&#039;/loc/imu&#039;&#039;.&lt;br /&gt;
* &#039;&#039;/loc/odom_in -&amp;gt; /fejemis/odom&#039;&#039;&lt;br /&gt;
* &#039;&#039;odometry/filtered -&amp;gt; odom&#039;&#039;, which resolves to &#039;&#039;/loc/odom&#039;&#039;.&lt;br /&gt;
* diagnostics are remapped to &#039;&#039;/loc/ekf/_diagnostics&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
The EKF configuration in &#039;&#039;config/odometry.yaml&#039;&#039; runs at 30 Hz, uses two-dimensional mode, publishes TF, and fuses:&lt;br /&gt;
&lt;br /&gt;
* Wheel odometry from &#039;&#039;/fejemis/odom&#039;&#039;.&lt;br /&gt;
* RTAB-Map ICP odometry from &#039;&#039;/loc/rtabmap_odom&#039;&#039;.&lt;br /&gt;
* Filtered IMU input is present as &#039;&#039;/loc/imu&#039;&#039;, although the current real configuration disables the IMU state variables in the EKF and relies more heavily on odometry sources for motion estimation.&lt;br /&gt;
&lt;br /&gt;
Simulation mode uses &#039;&#039;config/odometry_sim.yaml&#039;&#039; and avoids the Madgwick IMU orientation because the simulated IMU orientation can conflict with Gazebo odometry frame conventions.&lt;br /&gt;
&lt;br /&gt;
[[File:fejemis_maploc_ekf_block_diagram.png| 800px]]&lt;br /&gt;
&lt;br /&gt;
EKF input/output diagram: &#039;&#039;/fejemis/odom&#039;&#039;, &#039;&#039;/loc/rtabmap_odom&#039;&#039;, &#039;&#039;/loc/imu&#039;&#039; into &#039;&#039;/loc/ekf&#039;&#039;, output &#039;&#039;/loc/odom&#039;&#039; and TF.&lt;br /&gt;
&lt;br /&gt;
=== launch/realsense.launch.py ===&lt;br /&gt;
&lt;br /&gt;
Includes the official &#039;&#039;realsense2_camera&#039;&#039; launch file with the package&#039;s &#039;&#039;config/realsense.yaml&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
Launch arguments:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;camera_namespace&#039;&#039; - namespace for the camera. Default: empty string.&lt;br /&gt;
* &#039;&#039;use_sim_time&#039;&#039; - declared for consistency, although the included launch is mainly configured through the RealSense launch arguments.&lt;br /&gt;
&lt;br /&gt;
Important RealSense settings:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;publish_tf=true&#039;&#039;&lt;br /&gt;
* &#039;&#039;enable_sync=true&#039;&#039;&lt;br /&gt;
* &#039;&#039;unite_imu_method=2&#039;&#039;&lt;br /&gt;
* &#039;&#039;config_file=fejemis_maploc/config/realsense.yaml&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The camera publishes the RGB, depth, camera info, IMU, and TF data used by RTAB-Map and by the depth-to-scan obstacle conversion.&lt;br /&gt;
&lt;br /&gt;
=== launch/rtabmap_slam.launch.py ===&lt;br /&gt;
&lt;br /&gt;
Starts the RTAB-Map mapping/localisation pipeline and includes &#039;&#039;tf.launch.py&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
Launch arguments:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;use_sim_time&#039;&#039; - selects real or simulation config. Default: &#039;&#039;false&#039;&#039;.&lt;br /&gt;
* &#039;&#039;localization&#039;&#039; - if &#039;&#039;true&#039;&#039;, RTAB-Map loads an existing database and does not increment the map. If &#039;&#039;false&#039;&#039;, it starts in mapping mode and deletes the database on start. Default: &#039;&#039;false&#039;&#039;.&lt;br /&gt;
* &#039;&#039;namespace&#039;&#039; - namespace for RTAB-Map. Default: &#039;&#039;loc&#039;&#039;.&lt;br /&gt;
* &#039;&#039;map_location&#039;&#039; - selects the database path. &#039;&#039;lab&#039;&#039; maps to &#039;&#039;~/.ros/rtabmap.db&#039;&#039; and &#039;&#039;asta&#039;&#039; maps to &#039;&#039;~/.ros/rtabmap_asta.db&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
Sensor remappings:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;rgb/image -&amp;gt; /camera/color/image_raw&#039;&#039;&lt;br /&gt;
* &#039;&#039;rgb/camera_info -&amp;gt; /camera/color/camera_info&#039;&#039;&lt;br /&gt;
* &#039;&#039;depth/image -&amp;gt; /camera/depth/image_rect_raw&#039;&#039;&lt;br /&gt;
* &#039;&#039;depth/camera_info -&amp;gt; /camera/depth/camera_info&#039;&#039;&lt;br /&gt;
* &#039;&#039;imu -&amp;gt; /loc/imu&#039;&#039;&lt;br /&gt;
* &#039;&#039;scan -&amp;gt; /fejemis/scan&#039;&#039;&lt;br /&gt;
* &#039;&#039;initialpose -&amp;gt; /initialpose&#039;&#039;&lt;br /&gt;
* &#039;&#039;grid_prob_map -&amp;gt; /map&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Nodes:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;depthimage_to_laserscan_node&#039;&#039; converts the depth image into &#039;&#039;/camera_scan&#039;&#039;.&lt;br /&gt;
* &#039;&#039;rtabmap_sync/rgbd_sync&#039;&#039; synchronises RGB-D data on the real robot.&lt;br /&gt;
* &#039;&#039;rtabmap_odom/icp_odometry&#039;&#039; publishes ICP odometry to &#039;&#039;/loc/rtabmap_odom&#039;&#039; on the real robot.&lt;br /&gt;
* &#039;&#039;rtabmap_slam/rtabmap&#039;&#039; runs either SLAM mode or localisation mode.&lt;br /&gt;
&lt;br /&gt;
In SLAM mode, &#039;&#039;delete_db_on_start=True&#039;&#039; and RTAB-Map is launched with &#039;&#039;-d&#039;&#039;, so the selected database is overwritten. In localisation mode, &#039;&#039;Mem/IncrementalMemory=False&#039;&#039; and &#039;&#039;Mem/InitWMWithAllNodes=True&#039;&#039;, so the existing map is loaded for localisation.&lt;br /&gt;
&lt;br /&gt;
=== launch/map.launch.py ===&lt;br /&gt;
&lt;br /&gt;
Starts a component-based Nav2 map server in namespace &#039;&#039;loc&#039;&#039;, using &#039;&#039;config/nav2_servers.yaml&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
Launch arguments:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;use_sim_time&#039;&#039; - forwarded to the map server.&lt;br /&gt;
* &#039;&#039;map_make_container&#039;&#039;, &#039;&#039;map_container&#039;&#039;, and &#039;&#039;map_container_ns&#039;&#039; - declared as container-related options. The current launch creates a container named &#039;&#039;_map&#039;&#039; with prefix/namespace &#039;&#039;map&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
The map server is configured with:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;topic_name=/loc/map&#039;&#039;&lt;br /&gt;
* &#039;&#039;frame_id=map&#039;&#039;&lt;br /&gt;
* &#039;&#039;yaml_filename=&amp;quot;&amp;quot;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
In the current system, the map is primarily produced by RTAB-Map, while the Nav2 map server exposes map-server functionality for the navigation stack.&lt;br /&gt;
&lt;br /&gt;
=== launch/planning.launch.py ===&lt;br /&gt;
&lt;br /&gt;
Starts the Nav2 planning and navigation servers, the Fejemis velocity relay, the brush service, and optionally the OpenNav coverage server.&lt;br /&gt;
&lt;br /&gt;
Launch arguments:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;namespace&#039;&#039; - namespace for Nav2 nodes. Default: &#039;&#039;fejemis&#039;&#039;.&lt;br /&gt;
* &#039;&#039;use_sim_time&#039;&#039; - forwarded to nodes. Default: &#039;&#039;False&#039;&#039;.&lt;br /&gt;
* &#039;&#039;enable_coverage&#039;&#039; - enables &#039;&#039;opennav_coverage&#039;&#039;. The default is detected from whether the coverage packages are installed.&lt;br /&gt;
&lt;br /&gt;
Nodes:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;nav2_controller/controller_server&#039;&#039; named &#039;&#039;controller&#039;&#039;.&lt;br /&gt;
* &#039;&#039;fejemis_maploc/nav2_cmd_vel_deg_relay.py&#039;&#039;.&lt;br /&gt;
* &#039;&#039;nav2_behaviors/behavior_server&#039;&#039; named &#039;&#039;behavior&#039;&#039;.&lt;br /&gt;
* &#039;&#039;nav2_planner/planner_server&#039;&#039; named &#039;&#039;planner_server&#039;&#039;.&lt;br /&gt;
* &#039;&#039;nav2_bt_navigator/bt_navigator&#039;&#039; named &#039;&#039;bt_navigator&#039;&#039;.&lt;br /&gt;
* &#039;&#039;nav2_map_server/map_server&#039;&#039;.&lt;br /&gt;
* &#039;&#039;fejemis_maploc/brush_control_service.py&#039;&#039; in namespace &#039;&#039;control/brush&#039;&#039;, named &#039;&#039;controller&#039;&#039;.&lt;br /&gt;
* &#039;&#039;opennav_coverage/opennav_coverage&#039;&#039; named &#039;&#039;coverage_server&#039;&#039;, when enabled.&lt;br /&gt;
&lt;br /&gt;
Important remappings:&lt;br /&gt;
&lt;br /&gt;
* Nav2 odometry is remapped from &#039;&#039;/behavior/odom&#039;&#039; to &#039;&#039;/loc/odom&#039;&#039;.&lt;br /&gt;
* Nav2 map input is remapped from &#039;&#039;/behavior/map&#039;&#039; to &#039;&#039;/loc/map&#039;&#039;.&lt;br /&gt;
* Nav2 controller output &#039;&#039;cmd_vel&#039;&#039; is remapped to &#039;&#039;/control/nav2/cmd_vel&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
The velocity relay then converts angular velocity units and republishes to &#039;&#039;/control/joy/cmd_vel&#039;&#039; for the existing Fejemis control path.&lt;br /&gt;
&lt;br /&gt;
The Nav2 configuration in &#039;&#039;config/nav2_servers.yaml&#039;&#039; uses:&lt;br /&gt;
&lt;br /&gt;
* Regulated Pure Pursuit as the path follower.&lt;br /&gt;
* Navfn as the global planner.&lt;br /&gt;
* Local costmap sources from LiDAR, camera scan, net scan, and cable scan.&lt;br /&gt;
* Global costmap with static map, LiDAR obstacle layer, and inflation layer.&lt;br /&gt;
* The OpenNav coverage navigator when coverage support is enabled.&lt;br /&gt;
&lt;br /&gt;
=== launch/lifecycle.launch.py ===&lt;br /&gt;
&lt;br /&gt;
Starts Nav2 lifecycle managers.&lt;br /&gt;
&lt;br /&gt;
Launch arguments:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;in_sim&#039;&#039; - forwarded as &#039;&#039;use_sim_time&#039;&#039;.&lt;br /&gt;
* &#039;&#039;enable_loc_lfc&#039;&#039; - controls whether the localisation lifecycle manager is started. Default: &#039;&#039;false&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
Lifecycle managers:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;/fejemis/beh_lfc&#039;&#039; - behaviour/navigation lifecycle manager.&lt;br /&gt;
* &#039;&#039;/loc/loc_lfc&#039;&#039; - localisation lifecycle manager, only when &#039;&#039;enable_loc_lfc=true&#039;&#039;.&lt;br /&gt;
* &#039;&#039;/fejemis/hard_lfc&#039;&#039; - hardware lifecycle manager, disabled in simulation.&lt;br /&gt;
&lt;br /&gt;
Diagnostics are remapped to &#039;&#039;/diag/beh_lfc&#039;&#039;, &#039;&#039;/diag/loc_lfc&#039;&#039;, and &#039;&#039;/diag/hard_lfc&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
=== launch/lifecycle_managers_only.launch.py ===&lt;br /&gt;
&lt;br /&gt;
Starts the navigation-side launch files without the heavy sensor components. This is useful when another machine is responsible for LiDAR, RealSense, or RTAB-Map.&lt;br /&gt;
&lt;br /&gt;
Launch arguments:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;in_sim&#039;&#039; - enables simulation time.&lt;br /&gt;
* &#039;&#039;localization&#039;&#039; - when true, starts the planning stack on the main Pi. Default: &#039;&#039;false&#039;&#039;.&lt;br /&gt;
* &#039;&#039;enable_coverage&#039;&#039; - enables coverage navigation. Default: &#039;&#039;true&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
Included launch files:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;planning.launch.py&#039;&#039; is included only when &#039;&#039;in_sim=true&#039;&#039; or &#039;&#039;localization=true&#039;&#039;.&lt;br /&gt;
* &#039;&#039;lifecycle.launch.py&#039;&#039; is included immediately.&lt;br /&gt;
* &#039;&#039;behavior.launch.py&#039;&#039; is included after a 30 second delay, giving Nav2 servers time to become active before the BT engine connects to action servers.&lt;br /&gt;
&lt;br /&gt;
=== launch/behavior.launch.py ===&lt;br /&gt;
&lt;br /&gt;
Starts the Fejemis behaviour tree engine from &#039;&#039;raubase_core&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
Launch argument:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;use_sim_time&#039;&#039; - forwarded to the BT engine.&lt;br /&gt;
&lt;br /&gt;
The launch file selects the behaviour tree XML based on the ROS distribution:&lt;br /&gt;
&lt;br /&gt;
* Older BehaviourTree.CPP format: &#039;&#039;resources/behavior/main.xml&#039;&#039;.&lt;br /&gt;
* BehaviourTree.CPP v4 format: &#039;&#039;resources/behavior/main4.xml&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
The node launched is:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;raubase_core/bt_engine&#039;&#039;, configured by &#039;&#039;config/bt_engine.yaml&#039;&#039; in namespace &#039;&#039;fejemis&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
The BT plugin configuration loads:&lt;br /&gt;
&lt;br /&gt;
* raubase logging, mission, blackboard, coverage, and mixer plugins.&lt;br /&gt;
* Nav2 action plugins for single trigger, follow path, drive on heading, spin, and navigate to pose.&lt;br /&gt;
* OpenNav coverage path computation plugin.&lt;br /&gt;
* The package&#039;s custom &#039;&#039;fejemis_call_trigger_service_bt_node&#039;&#039; plugin.&lt;br /&gt;
&lt;br /&gt;
=== launch/robot.launch.py inclusion ===&lt;br /&gt;
&lt;br /&gt;
The top-level &#039;&#039;fejemis&#039;&#039; package includes &#039;&#039;fejemis_maploc&#039;&#039; from &#039;&#039;fejemis/launch/robot.launch.py&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
Typical distribution:&lt;br /&gt;
&lt;br /&gt;
* Main Pi: bridge control, odometry, lifecycle managers, behaviour tree, and planning when localisation is enabled.&lt;br /&gt;
* Auxiliary Pi: LiDAR, RTAB-Map, RealSense, and optional vision.&lt;br /&gt;
&lt;br /&gt;
The top-level launch waits 3 seconds after bridge startup before launching the rest of the stack.&lt;br /&gt;
&lt;br /&gt;
== Script Nodes ==&lt;br /&gt;
&lt;br /&gt;
=== nav2_cmd_vel_deg_relay.py ===&lt;br /&gt;
&lt;br /&gt;
Purpose: adapts Nav2 velocity commands to the Fejemis control convention.&lt;br /&gt;
&lt;br /&gt;
Subscriptions:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;/control/nav2/cmd_vel&#039;&#039; - &#039;&#039;geometry_msgs/Twist&#039;&#039;, with angular velocity in rad/s.&lt;br /&gt;
&lt;br /&gt;
Publications:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;/control/joy/cmd_vel&#039;&#039; - &#039;&#039;geometry_msgs/Twist&#039;&#039;, with angular velocity converted to deg/s.&lt;br /&gt;
&lt;br /&gt;
The linear fields are copied unchanged. The angular x, y, and z fields are multiplied by 180/pi. This is needed because Nav2 uses the normal ROS convention of rad/s, while the existing Fejemis bridge control path expects angular velocity in deg/s.&lt;br /&gt;
&lt;br /&gt;
=== brush_control_service.py ===&lt;br /&gt;
&lt;br /&gt;
Purpose: exposes simple ROS services for turning the brush on and off.&lt;br /&gt;
&lt;br /&gt;
Parameters:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;manage_topic&#039;&#039; - topic for &#039;&#039;fejemis_bridge/msg/SteerManage&#039;&#039;. Default: &#039;&#039;/fejemis/front/manage&#039;&#039;.&lt;br /&gt;
* &#039;&#039;qos_depth&#039;&#039; - publisher QoS depth. Default: &#039;&#039;10&#039;&#039;.&lt;br /&gt;
* &#039;&#039;initial_brush_on&#039;&#039; - initial internal state. Default: &#039;&#039;False&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
Publications:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;manage_topic&#039;&#039; - publishes &#039;&#039;SteerManage.BRUSH_ON&#039;&#039; or &#039;&#039;SteerManage.BRUSH_OFF&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
Services:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;~/set&#039;&#039; - &#039;&#039;std_srvs/SetBool&#039;&#039;, true means brush on and false means brush off.&lt;br /&gt;
* &#039;&#039;~/on&#039;&#039; - &#039;&#039;std_srvs/Trigger&#039;&#039;, turns the brush on.&lt;br /&gt;
* &#039;&#039;~/off&#039;&#039; - &#039;&#039;std_srvs/Trigger&#039;&#039;, turns the brush off.&lt;br /&gt;
&lt;br /&gt;
In &#039;&#039;planning.launch.py&#039;&#039; this node runs as &#039;&#039;/control/brush/controller&#039;&#039;, which creates the services used by the behaviour tree:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;/control/brush/controller/on&#039;&#039;&lt;br /&gt;
* &#039;&#039;/control/brush/controller/off&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
=== rviz_goal_relay.py ===&lt;br /&gt;
&lt;br /&gt;
Purpose: lets an operator send a Nav2 goal from RViz.&lt;br /&gt;
&lt;br /&gt;
Subscriptions:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;/goal_pose&#039;&#039; - RViz 2D Nav Goal as &#039;&#039;geometry_msgs/PoseStamped&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
Action clients:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;/fejemis/navigate_to_pose&#039;&#039; - &#039;&#039;nav2_msgs/action/NavigateToPose&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
When a goal is clicked in RViz, the node checks that the action server is ready, forwards the goal, and logs whether it was accepted and whether it succeeded.&lt;br /&gt;
&lt;br /&gt;
=== mission_trigger.py ===&lt;br /&gt;
&lt;br /&gt;
Purpose: starts a cleaning mission by publishing blackboard values and then setting the active mission.&lt;br /&gt;
&lt;br /&gt;
Parameters:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;mission&#039;&#039; - mission string to publish. Default: &#039;&#039;cleaning/waypoint&#039;&#039;.&lt;br /&gt;
* &#039;&#039;dock_pose&#039;&#039; - dock pose as &#039;&#039;[x, y, yaw]&#039;&#039;. Default: &#039;&#039;[2.62, 3.9, 0.0]&#039;&#039;.&lt;br /&gt;
* &#039;&#039;waypoint_pose&#039;&#039; - waypoint pose as &#039;&#039;[x, y, yaw]&#039;&#039;. Default: &#039;&#039;[2.10491, 0.497438, 3.11127]&#039;&#039;.&lt;br /&gt;
* &#039;&#039;clean_poly&#039;&#039; - polygon string used for cleaning area blackboard values.&lt;br /&gt;
* &#039;&#039;startup_delay&#039;&#039; - delay before publishing. Default: &#039;&#039;2.0&#039;&#039; seconds.&lt;br /&gt;
&lt;br /&gt;
Publications:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;/blackboard/dock_x&#039;&#039;, &#039;&#039;/blackboard/dock_y&#039;&#039;, &#039;&#039;/blackboard/dock_yaw&#039;&#039;&lt;br /&gt;
* &#039;&#039;/blackboard/waypoint_x&#039;&#039;, &#039;&#039;/blackboard/waypoint_y&#039;&#039;, &#039;&#039;/blackboard/waypoint_yaw&#039;&#039;&lt;br /&gt;
* &#039;&#039;/blackboard/field_polygon&#039;&#039;&lt;br /&gt;
* &#039;&#039;/blackboard/clean_poly&#039;&#039;&lt;br /&gt;
* &#039;&#039;/blackboard/start_pose&#039;&#039;&lt;br /&gt;
* &#039;&#039;/blackboard/dock_pose&#039;&#039;&lt;br /&gt;
* &#039;&#039;/blackboard/waypoint_pose&#039;&#039;&lt;br /&gt;
* &#039;&#039;/behavior/set_mission&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The blackboard publishers use transient-local QoS so late subscribers can still receive the latest values. Pose strings are encoded as:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
stamp;frame;x;y;z;qx;qy;qz;qw&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The node also subscribes to &#039;&#039;/behavior/set_mission&#039;&#039;. When it sees &#039;&#039;auto/idle&#039;&#039; after the mission has run, it logs completion and shuts down.&lt;br /&gt;
&lt;br /&gt;
=== complete_coverage_planner.py ===&lt;br /&gt;
&lt;br /&gt;
Purpose: interactive coverage test node for RViz.&lt;br /&gt;
&lt;br /&gt;
Parameters:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;rviz_click_topic&#039;&#039; - clicked point topic. Default: &#039;&#039;/clicked_point&#039;&#039;.&lt;br /&gt;
* &#039;&#039;coverage_frame&#039;&#039; - frame for the coverage polygon. Default: &#039;&#039;map&#039;&#039;.&lt;br /&gt;
* &#039;&#039;min_polygon_points&#039;&#039; - number of clicked points before sending a goal. Default: &#039;&#039;4&#039;&#039;.&lt;br /&gt;
* &#039;&#039;auto_close_polygon&#039;&#039; - whether to append the first point as the final point. Default: &#039;&#039;True&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
Subscriptions:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;/clicked_point&#039;&#039; by default - RViz point clicks.&lt;br /&gt;
&lt;br /&gt;
Action clients:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;fejemis/navigate_complete_coverage&#039;&#039; - &#039;&#039;opennav_coverage_msgs/action/NavigateCompleteCoverage&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
The node waits for &#039;&#039;fejemis/bt_navigator&#039;&#039; to become active, collects clicked points in RViz, converts them to a &#039;&#039;geometry_msgs/Polygon&#039;&#039;, and sends a coverage navigation action once enough points have been collected.&lt;br /&gt;
&lt;br /&gt;
=== fixed_complete_coverage_planner.py ===&lt;br /&gt;
&lt;br /&gt;
Purpose: fixed-polygon coverage test node.&lt;br /&gt;
&lt;br /&gt;
Parameters:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;coverage_frame&#039;&#039; - frame for the coverage polygon. Default: &#039;&#039;map&#039;&#039;.&lt;br /&gt;
* &#039;&#039;min_polygon_points&#039;&#039; - retained from the interactive tester. Default: &#039;&#039;4&#039;&#039;.&lt;br /&gt;
* &#039;&#039;auto_close_polygon&#039;&#039; - whether to append the first point as the final point. Default: &#039;&#039;True&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
Action clients:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;fejemis/navigate_complete_coverage&#039;&#039; - &#039;&#039;opennav_coverage_msgs/action/NavigateCompleteCoverage&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
The node waits for &#039;&#039;fejemis/bt_navigator&#039;&#039; to become active, then sends a fixed four-point polygon:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
(-5.35,  2.45)&lt;br /&gt;
( 2.83,  3.18)&lt;br /&gt;
( 3.22, -5.08)&lt;br /&gt;
(-4.76, -5.97)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This is useful for testing coverage planning without clicking points manually in RViz.&lt;br /&gt;
&lt;br /&gt;
== C++ Behaviour Tree Plugin ==&lt;br /&gt;
&lt;br /&gt;
=== fejemis_call_trigger_service_bt_node ===&lt;br /&gt;
&lt;br /&gt;
The C++ plugin &#039;&#039;src/bt_plugins/call_trigger_service_bt_node.cpp&#039;&#039; registers the behaviour tree node &#039;&#039;CallTriggerService&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
Inputs:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;service_name&#039;&#039; - service to call.&lt;br /&gt;
* &#039;&#039;wait_for_service_ms&#039;&#039; - timeout while waiting for service availability. Default: &#039;&#039;1000&#039;&#039;.&lt;br /&gt;
* &#039;&#039;call_timeout_ms&#039;&#039; - timeout for the service call. Default: &#039;&#039;2000&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
The node creates a &#039;&#039;std_srvs/Trigger&#039;&#039; client, waits for the target service, calls it, and returns:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;SUCCESS&#039;&#039; when the service exists, responds before timeout, and returns &#039;&#039;success=true&#039;&#039;.&lt;br /&gt;
* &#039;&#039;FAILURE&#039;&#039; when the service is unavailable, the call times out, or the response reports failure.&lt;br /&gt;
&lt;br /&gt;
The cleaning subtree uses it to call:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;/control/brush/controller/on&#039;&#039;&lt;br /&gt;
* &#039;&#039;/control/brush/controller/off&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
== Behaviour Tree ==&lt;br /&gt;
&lt;br /&gt;
The active behaviour tree on newer ROS distributions is &#039;&#039;resources/behavior/main4.xml&#039;&#039;. It is loaded by &#039;&#039;behavior.launch.py&#039;&#039; through the &#039;&#039;raubase_core/bt_engine&#039;&#039; node.&lt;br /&gt;
&lt;br /&gt;
=== Main Loop ===&lt;br /&gt;
&lt;br /&gt;
The root tree is &#039;&#039;main&#039;&#039;:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
main&lt;br /&gt;
`-- Sequence&lt;br /&gt;
    |-- SingleTrigger&lt;br /&gt;
    |   `-- t_init&lt;br /&gt;
    `-- KeepRunningUntilFailure&lt;br /&gt;
        `-- ReactiveSequence&lt;br /&gt;
            |-- ForceSuccess&lt;br /&gt;
            |   `-- t_passive&lt;br /&gt;
            `-- t_runtime&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The structure means:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;t_init&#039;&#039; runs once at startup.&lt;br /&gt;
* &#039;&#039;t_passive&#039;&#039; runs repeatedly and updates blackboard/mission state.&lt;br /&gt;
* &#039;&#039;t_runtime&#039;&#039; selects the active behaviour based on the current mission.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Visualization placeholder:&#039;&#039;&#039; exported Groot image of &#039;&#039;main4.xml&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
=== t_init ===&lt;br /&gt;
&lt;br /&gt;
Initialises the robot behaviour state:&lt;br /&gt;
&lt;br /&gt;
* Sets mission to &#039;&#039;auto/idle&#039;&#039;.&lt;br /&gt;
* Sets blackboard key &#039;&#039;initialized=1&#039;&#039;.&lt;br /&gt;
* Sets calibration square distance &#039;&#039;calib_sq_dist=7&#039;&#039;.&lt;br /&gt;
* Opens the mixer port using &#039;&#039;OpenMixerPort&#039;&#039; with priority 4 and topic &#039;&#039;/behavior&#039;&#039;.&lt;br /&gt;
* Logs that the robot is initialized.&lt;br /&gt;
&lt;br /&gt;
=== t_passive ===&lt;br /&gt;
&lt;br /&gt;
Runs as the passive safety/update loop:&lt;br /&gt;
&lt;br /&gt;
* Logs &#039;&#039;Safety loop&#039;&#039; at debug level.&lt;br /&gt;
* Refreshes &#039;&#039;start_pose&#039;&#039;, &#039;&#039;waypoint_pose&#039;&#039;, &#039;&#039;clean_poly&#039;&#039;, and &#039;&#039;field_polygon&#039;&#039; from &#039;&#039;/blackboard/...&#039;&#039; topics.&lt;br /&gt;
* Refreshes the current mission from &#039;&#039;/behavior/set_mission&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
=== t_runtime ===&lt;br /&gt;
&lt;br /&gt;
Selects behaviour using a reactive fallback:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
t_runtime&lt;br /&gt;
|-- t_idle_runtime&lt;br /&gt;
|-- t_calib&lt;br /&gt;
|-- t_mapping&lt;br /&gt;
|-- t_cleaning&lt;br /&gt;
`-- unknown mission handler -&amp;gt; set idle&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The first matching mission branch runs. If no known branch matches, the tree logs an error and returns to idle.&lt;br /&gt;
&lt;br /&gt;
=== Idle ===&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;t_idle_runtime&#039;&#039; succeeds when the current mission is:&lt;br /&gt;
&lt;br /&gt;
* mission: &#039;&#039;auto&#039;&#039;&lt;br /&gt;
* mission_sub: &#039;&#039;idle&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
This keeps the robot in a waiting state. Sensors, telemetry, and subscriptions remain active, but no movement commands are issued. The robot leaves this state when a new mission is received through &#039;&#039;/behavior/set_mission&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
=== Calibration ===&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;t_calib&#039;&#039; chooses between line calibration and square calibration.&lt;br /&gt;
&lt;br /&gt;
Line calibration, &#039;&#039;t_calib_line&#039;&#039;:&lt;br /&gt;
&lt;br /&gt;
* Requires mission &#039;&#039;calib/line&#039;&#039;.&lt;br /&gt;
* Drives forward using &#039;&#039;DriveOnHeading&#039;&#039; &lt;br /&gt;
* &#039;&#039;calib_sq_dist&#039;&#039; is set to 0.05 m/s during initialization.&lt;br /&gt;
* Returns to idle.&lt;br /&gt;
&lt;br /&gt;
Square calibration, &#039;&#039;t_calib_square&#039;&#039;:&lt;br /&gt;
&lt;br /&gt;
* Requires mission &#039;&#039;calib/*&#039;&#039;.&lt;br /&gt;
* Runs &#039;&#039;f_calib_side&#039;&#039; four times.&lt;br /&gt;
* Each side drives forward and spins 1.57 rad.&lt;br /&gt;
* Returns to idle.&lt;br /&gt;
Each &#039;&#039;f_calib_side&#039;&#039; contains:&lt;br /&gt;
&lt;br /&gt;
*DriveOnHeading&lt;br /&gt;
*Spin&lt;br /&gt;
*A quarter-turn style calibration step&lt;br /&gt;
Calibration succeeds when each action in the sequence completes successfully.&lt;br /&gt;
&lt;br /&gt;
=== Mapping ===&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;t_mapping&#039;&#039; currently checks for &#039;&#039;mission mapping/*&#039;&#039; and then returns to idle.&lt;br /&gt;
&lt;br /&gt;
*Requires mission mapping/*&lt;br /&gt;
&lt;br /&gt;
*Currently acts as a placeholder&lt;br /&gt;
&lt;br /&gt;
*Does not yet launch the SLAM or exploration pipeline&lt;br /&gt;
&lt;br /&gt;
*Returns to idle immediately&lt;br /&gt;
&lt;br /&gt;
This branch should later be expanded to perform autonomous mapping, localization checks, and map saving.&lt;br /&gt;
&lt;br /&gt;
=== Cleaning ===&lt;br /&gt;
&lt;br /&gt;
The current cleaning branch is &#039;&#039;t_cleaning&#039;&#039;:&lt;br /&gt;
&lt;br /&gt;
* Requires mission &#039;&#039;cleaning/waypoint&#039;&#039; with mission_sub &#039;&#039;idle&#039;&#039;.&lt;br /&gt;
* Navigates to &#039;&#039;{waypoint_pose}&#039;&#039; using &#039;&#039;NavigateToPose&#039;&#039;.&lt;br /&gt;
* Sets current mission to &#039;&#039;cleaning/area&#039;&#039;.&lt;br /&gt;
* Returns to idle.&lt;br /&gt;
&lt;br /&gt;
Supporting cleaning subtrees are already present:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;f_get_area_to_clean&#039;&#039; sets a polygon and calls &#039;&#039;ComputeCoveragePath&#039;&#039;.&lt;br /&gt;
* &#039;&#039;f_clean_area&#039;&#039; turns the brush on, follows &#039;&#039;{cover_nav_path}&#039;&#039;, and turns the brush off.&lt;br /&gt;
* &#039;&#039;f_go_to_path_start&#039;&#039; is a placeholder that logs navigation to the path start.&lt;br /&gt;
&lt;br /&gt;
These subtrees show the intended full cleaning sequence, although the active &#039;&#039;t_cleaning&#039;&#039; branch currently only navigates to a waypoint and changes mission state.&lt;br /&gt;
[[File:cleaning.png| 800px]]&lt;br /&gt;
&lt;br /&gt;
Cleanign Node Behaviour Sequence: mission trigger -&amp;gt; waypoint navigation -&amp;gt; coverage path computation -&amp;gt; brush on -&amp;gt; follow coverage path -&amp;gt; brush off -&amp;gt; idle.&lt;br /&gt;
&lt;br /&gt;
== Configuration Files ==&lt;br /&gt;
&lt;br /&gt;
=== config/odometry.yaml and config/odometry_sim.yaml ===&lt;br /&gt;
&lt;br /&gt;
Configure the EKF and IMU filter. The real configuration fuses wheel odometry and RTAB-Map ICP odometry in 2D mode. The simulation configuration is separated to avoid conflicting simulated IMU orientation.&lt;br /&gt;
&lt;br /&gt;
=== config/rtabmap.yaml and config/rtabmap_sim.yaml ===&lt;br /&gt;
&lt;br /&gt;
Configure RTAB-Map for real robot and simulation use. &#039;&#039;rtabmap_slam.launch.py&#039;&#039; selects between them using &#039;&#039;use_sim_time&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
=== config/nav2_servers.yaml and config/nav2_sim.yaml ===&lt;br /&gt;
&lt;br /&gt;
Configure Nav2 controller, planner, behaviour server, costmaps, BT navigator, coverage server, and map server. The main real-robot launch uses &#039;&#039;nav2_servers.yaml&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
=== config/bt_engine.yaml ===&lt;br /&gt;
&lt;br /&gt;
Lists behaviour tree plugins loaded by &#039;&#039;raubase_core/bt_engine&#039;&#039;, including the Fejemis custom trigger-service plugin.&lt;br /&gt;
&lt;br /&gt;
=== config/lifecycle.yaml ===&lt;br /&gt;
&lt;br /&gt;
Configures lifecycle manager node lists and autostart behaviour for the navigation, localisation, and hardware lifecycle managers.&lt;br /&gt;
&lt;br /&gt;
=== config/realsense.yaml ===&lt;br /&gt;
&lt;br /&gt;
RealSense camera configuration loaded by &#039;&#039;realsense.launch.py&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
== Typical Usage ==&lt;br /&gt;
&lt;br /&gt;
Start the complete robot stack through the top-level launch:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ros2 launch fejemis robot.launch.py localization:=true map_location:=asta&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Run on both Raspberry Pis. The launch file decides which parts run on the main and auxiliary computers through the &#039;&#039;is_aux&#039;&#039; argument.&lt;br /&gt;
&lt;br /&gt;
Start a waypoint cleaning mission after the system is up:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ros2 run fejemis_maploc mission_trigger.py&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Send an RViz navigation goal:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ros2 run fejemis_maploc rviz_goal_relay.py&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Test coverage navigation with RViz clicked points:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ros2 run fejemis_maploc complete_coverage_planner.py&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Test coverage navigation with the fixed polygon:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ros2 run fejemis_maploc fixed_complete_coverage_planner.py&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Notes and Future Work ==&lt;br /&gt;
&lt;br /&gt;
* Clarify which Raspberry Pi should run each launch file in the final deployment.&lt;br /&gt;
* Expand the cleaning behaviour section once &#039;&#039;t_cleaning&#039;&#039; is connected to &#039;&#039;f_get_area_to_clean&#039;&#039; and &#039;&#039;f_clean_area&#039;&#039; in the active tree.&lt;/div&gt;</summary>
		<author><name>S252724</name></author>
	</entry>
	<entry>
		<id>https://rsewiki.electro.dtu.dk/index.php?title=File:Cleaning.png&amp;diff=8969</id>
		<title>File:Cleaning.png</title>
		<link rel="alternate" type="text/html" href="https://rsewiki.electro.dtu.dk/index.php?title=File:Cleaning.png&amp;diff=8969"/>
		<updated>2026-05-28T10:25:23Z</updated>

		<summary type="html">&lt;p&gt;S252724: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>S252724</name></author>
	</entry>
	<entry>
		<id>https://rsewiki.electro.dtu.dk/index.php?title=Fejemis_Maploc_Ros2&amp;diff=8968</id>
		<title>Fejemis Maploc Ros2</title>
		<link rel="alternate" type="text/html" href="https://rsewiki.electro.dtu.dk/index.php?title=Fejemis_Maploc_Ros2&amp;diff=8968"/>
		<updated>2026-05-28T10:13:43Z</updated>

		<summary type="html">&lt;p&gt;S252724: /* Mapping */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Back to [[Fejemis ROS2 Software]]&lt;br /&gt;
&lt;br /&gt;
== Purpose ==&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fejemis_maploc&#039;&#039; is the mapping, localisation, navigation, and behaviour package for Fejemis. It connects the robot&#039;s sensor data to the navigation stack and exposes the high-level behaviour tree used to run calibration, mapping, waypoint navigation, and cleaning missions.&lt;br /&gt;
&lt;br /&gt;
The package sits between the low-level bridge and the high-level mission logic:&lt;br /&gt;
&lt;br /&gt;
* The bridge publishes wheel odometry, IMU data, and accepts velocity / brush commands.&lt;br /&gt;
* &#039;&#039;fejemis_maploc&#039;&#039; filters odometry, starts LiDAR and RealSense sensing, runs RTAB-Map, starts Nav2, and runs the Fejemis behaviour tree.&lt;br /&gt;
* The behaviour tree sends commands to Nav2, the coverage planner, the mixer, and the brush service.&lt;br /&gt;
[[File:maploc_general_block_diagram.png| 1600px]]&lt;br /&gt;
&lt;br /&gt;
Overall &#039;&#039;fejemis_maploc&#039;&#039; data flow from bridge, LiDAR, and RealSense into odometry, RTAB-Map, Nav2, coverage planning, behaviour tree, and robot commands.&lt;br /&gt;
&lt;br /&gt;
== Package Layout ==&lt;br /&gt;
&lt;br /&gt;
Important package folders:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;resources/launch&#039;&#039; - ROS 2 launch files for sensors, odometry, SLAM/localisation, planning, lifecycle managers, and behaviour trees.&lt;br /&gt;
* &#039;&#039;resources/config&#039;&#039; - YAML configuration for odometry, Nav2, RTAB-Map, RealSense, lifecycle managers, and BT plugins.&lt;br /&gt;
* &#039;&#039;resources/behavior&#039;&#039; - behaviour tree XML files. &#039;&#039;main.xml&#039;&#039; is the older BT.CPP format and &#039;&#039;main4.xml&#039;&#039; is the BT.CPP v4 version used on newer ROS distributions.&lt;br /&gt;
* &#039;&#039;scripts&#039;&#039; - Python helper nodes for mission triggering, RViz goal relays, brush services, velocity conversion, and coverage tests.&lt;br /&gt;
* &#039;&#039;src/bt_plugins&#039;&#039; - C++ behaviour tree plugin used by the cleaning behaviour.&lt;br /&gt;
&lt;br /&gt;
== Main Topics and Frames ==&lt;br /&gt;
&lt;br /&gt;
Common frame names:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;map&#039;&#039; - global map frame produced by RTAB-Map / Nav2.&lt;br /&gt;
* &#039;&#039;odom&#039;&#039; - local odometry frame.&lt;br /&gt;
* &#039;&#039;base_link&#039;&#039; - robot base frame.&lt;br /&gt;
* &#039;&#039;lidar_link&#039;&#039; - LiDAR frame.&lt;br /&gt;
* &#039;&#039;camera_camera_link&#039;&#039; and RealSense optical frames - camera frames used by the RealSense driver and RTAB-Map.&lt;br /&gt;
&lt;br /&gt;
Important topics and actions:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;/fejemis/odom&#039;&#039; - wheel odometry from the bridge.&lt;br /&gt;
* &#039;&#039;/fejemis/imu&#039;&#039; and &#039;&#039;/fejemis/mag&#039;&#039; - raw IMU and magnetometer inputs.&lt;br /&gt;
* &#039;&#039;/loc/imu&#039;&#039; - filtered IMU output from Madgwick.&lt;br /&gt;
* &#039;&#039;/loc/odom&#039;&#039; - fused odometry output from the EKF.&lt;br /&gt;
* &#039;&#039;/fejemis/scan&#039;&#039; - LiDAR scan.&lt;br /&gt;
* &#039;&#039;/camera/color/image_raw&#039;&#039;, &#039;&#039;/camera/depth/image_rect_raw&#039;&#039;, and camera info topics - RealSense RGB-D input.&lt;br /&gt;
* &#039;&#039;/camera_scan&#039;&#039; - depth-image-to-laserscan output used as an extra obstacle source.&lt;br /&gt;
* &#039;&#039;/loc/map&#039;&#039; - occupancy map used by Nav2.&lt;br /&gt;
* &#039;&#039;/control/nav2/cmd_vel&#039;&#039; - Nav2 velocity command before Fejemis-specific conversion.&lt;br /&gt;
* &#039;&#039;/control/joy/cmd_vel&#039;&#039; - velocity command sent into the existing Fejemis control path.&lt;br /&gt;
* &#039;&#039;/behavior/set_mission&#039;&#039; - mission command topic consumed by the behaviour tree.&lt;br /&gt;
* &#039;&#039;/blackboard/...&#039;&#039; - blackboard values mirrored into the behaviour tree.&lt;br /&gt;
* &#039;&#039;/fejemis/navigate_to_pose&#039;&#039; - Nav2 action used for waypoint goals.&lt;br /&gt;
* &#039;&#039;/fejemis/navigate_complete_coverage&#039;&#039; - OpenNav coverage action used for coverage missions.&lt;br /&gt;
* &#039;&#039;/control/brush/controller/on&#039;&#039; and &#039;&#039;/control/brush/controller/off&#039;&#039; - brush control trigger services.&lt;br /&gt;
&lt;br /&gt;
== Launch Files ==&lt;br /&gt;
&lt;br /&gt;
The package is designed so the top-level &#039;&#039;fejemis/launch/robot.launch.py&#039;&#039; can include only the parts needed on each Raspberry Pi. The main Pi typically handles bridge control and odometry, while the auxiliary Pi handles heavier sensing such as LiDAR, RealSense, RTAB-Map, and vision.&lt;br /&gt;
&lt;br /&gt;
=== launch/lidar.launch.py ===&lt;br /&gt;
&lt;br /&gt;
Starts the LD19 LiDAR driver from &#039;&#039;ldlidar_stl_ros2&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
Launch arguments:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;ld_make_container&#039;&#039; - whether the LiDAR launch should create a component container. Default: &#039;&#039;True&#039;&#039;.&lt;br /&gt;
* &#039;&#039;ld_container&#039;&#039; - component container name. Default: &#039;&#039;_laser&#039;&#039;.&lt;br /&gt;
* &#039;&#039;ld_container_ns&#039;&#039; - namespace for the component container. Default: &#039;&#039;loc&#039;&#039;.&lt;br /&gt;
* &#039;&#039;port_name&#039;&#039; - serial port for the LiDAR. Default: &#039;&#039;/dev/ttyUSB0&#039;&#039;.&lt;br /&gt;
* &#039;&#039;namespace&#039;&#039; - launch argument declared for compatibility. The included launch currently receives &#039;&#039;fejemis&#039;&#039; as namespace.&lt;br /&gt;
&lt;br /&gt;
Important configuration passed to the driver:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;laser_frame=lidar_link&#039;&#039;&lt;br /&gt;
* &#039;&#039;clockwise_dir=false&#039;&#039;&lt;br /&gt;
* &#039;&#039;range_min=0.2&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The resulting scan is used by RTAB-Map and by the Nav2 costmaps as the main 2D obstacle source.&lt;br /&gt;
&lt;br /&gt;
=== launch/tf.launch.py ===&lt;br /&gt;
&lt;br /&gt;
Publishes static transforms for the physical sensor layout.&lt;br /&gt;
&lt;br /&gt;
Published transforms:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;base_link -&amp;gt; camera_camera_link&#039;&#039; at approximately x=-0.24 m, y=0.0 m, z=0.44 m.&lt;br /&gt;
* On the real robot, &#039;&#039;base_link -&amp;gt; lidar_link&#039;&#039; at approximately x=-0.29 m, y=0.0 m, z=0.25 m, yaw=-1.5708 rad. The yaw correction compensates for the LiDAR&#039;s physical mounting angle.&lt;br /&gt;
* In simulation, &#039;&#039;laser_frame -&amp;gt; lidar_link&#039;&#039; is published instead, because Gazebo already provides &#039;&#039;laser_frame&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
The launch file uses the &#039;&#039;in_sim&#039;&#039; launch configuration to select the real or simulated LiDAR transform.&lt;br /&gt;
&lt;br /&gt;
=== launch/odometry.launch.py ===&lt;br /&gt;
&lt;br /&gt;
Starts the odometry estimation path. On the real robot it runs both the Madgwick IMU filter and the robot_localization EKF. In simulation it only runs the EKF with the simulation-specific configuration.&lt;br /&gt;
&lt;br /&gt;
Launch argument:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;use_sim_time&#039;&#039; - selects real or simulated odometry configuration. Default: &#039;&#039;False&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
Real robot nodes:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;imu_filter_madgwick_node&#039;&#039; named &#039;&#039;imu_filter&#039;&#039; in namespace &#039;&#039;loc&#039;&#039;.&lt;br /&gt;
* &#039;&#039;robot_localization/ekf_node&#039;&#039; named &#039;&#039;ekf&#039;&#039; in namespace &#039;&#039;loc&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
Real robot remappings:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;imu/mag -&amp;gt; /fejemis/mag&#039;&#039;&lt;br /&gt;
* &#039;&#039;imu/data_raw -&amp;gt; /fejemis/imu&#039;&#039;&lt;br /&gt;
* &#039;&#039;imu/data -&amp;gt; imu&#039;&#039;, which resolves to &#039;&#039;/loc/imu&#039;&#039;.&lt;br /&gt;
* &#039;&#039;/loc/odom_in -&amp;gt; /fejemis/odom&#039;&#039;&lt;br /&gt;
* &#039;&#039;odometry/filtered -&amp;gt; odom&#039;&#039;, which resolves to &#039;&#039;/loc/odom&#039;&#039;.&lt;br /&gt;
* diagnostics are remapped to &#039;&#039;/loc/ekf/_diagnostics&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
The EKF configuration in &#039;&#039;config/odometry.yaml&#039;&#039; runs at 30 Hz, uses two-dimensional mode, publishes TF, and fuses:&lt;br /&gt;
&lt;br /&gt;
* Wheel odometry from &#039;&#039;/fejemis/odom&#039;&#039;.&lt;br /&gt;
* RTAB-Map ICP odometry from &#039;&#039;/loc/rtabmap_odom&#039;&#039;.&lt;br /&gt;
* Filtered IMU input is present as &#039;&#039;/loc/imu&#039;&#039;, although the current real configuration disables the IMU state variables in the EKF and relies more heavily on odometry sources for motion estimation.&lt;br /&gt;
&lt;br /&gt;
Simulation mode uses &#039;&#039;config/odometry_sim.yaml&#039;&#039; and avoids the Madgwick IMU orientation because the simulated IMU orientation can conflict with Gazebo odometry frame conventions.&lt;br /&gt;
&lt;br /&gt;
[[File:fejemis_maploc_ekf_block_diagram.png| 800px]]&lt;br /&gt;
&lt;br /&gt;
EKF input/output diagram: &#039;&#039;/fejemis/odom&#039;&#039;, &#039;&#039;/loc/rtabmap_odom&#039;&#039;, &#039;&#039;/loc/imu&#039;&#039; into &#039;&#039;/loc/ekf&#039;&#039;, output &#039;&#039;/loc/odom&#039;&#039; and TF.&lt;br /&gt;
&lt;br /&gt;
=== launch/realsense.launch.py ===&lt;br /&gt;
&lt;br /&gt;
Includes the official &#039;&#039;realsense2_camera&#039;&#039; launch file with the package&#039;s &#039;&#039;config/realsense.yaml&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
Launch arguments:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;camera_namespace&#039;&#039; - namespace for the camera. Default: empty string.&lt;br /&gt;
* &#039;&#039;use_sim_time&#039;&#039; - declared for consistency, although the included launch is mainly configured through the RealSense launch arguments.&lt;br /&gt;
&lt;br /&gt;
Important RealSense settings:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;publish_tf=true&#039;&#039;&lt;br /&gt;
* &#039;&#039;enable_sync=true&#039;&#039;&lt;br /&gt;
* &#039;&#039;unite_imu_method=2&#039;&#039;&lt;br /&gt;
* &#039;&#039;config_file=fejemis_maploc/config/realsense.yaml&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The camera publishes the RGB, depth, camera info, IMU, and TF data used by RTAB-Map and by the depth-to-scan obstacle conversion.&lt;br /&gt;
&lt;br /&gt;
=== launch/rtabmap_slam.launch.py ===&lt;br /&gt;
&lt;br /&gt;
Starts the RTAB-Map mapping/localisation pipeline and includes &#039;&#039;tf.launch.py&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
Launch arguments:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;use_sim_time&#039;&#039; - selects real or simulation config. Default: &#039;&#039;false&#039;&#039;.&lt;br /&gt;
* &#039;&#039;localization&#039;&#039; - if &#039;&#039;true&#039;&#039;, RTAB-Map loads an existing database and does not increment the map. If &#039;&#039;false&#039;&#039;, it starts in mapping mode and deletes the database on start. Default: &#039;&#039;false&#039;&#039;.&lt;br /&gt;
* &#039;&#039;namespace&#039;&#039; - namespace for RTAB-Map. Default: &#039;&#039;loc&#039;&#039;.&lt;br /&gt;
* &#039;&#039;map_location&#039;&#039; - selects the database path. &#039;&#039;lab&#039;&#039; maps to &#039;&#039;~/.ros/rtabmap.db&#039;&#039; and &#039;&#039;asta&#039;&#039; maps to &#039;&#039;~/.ros/rtabmap_asta.db&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
Sensor remappings:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;rgb/image -&amp;gt; /camera/color/image_raw&#039;&#039;&lt;br /&gt;
* &#039;&#039;rgb/camera_info -&amp;gt; /camera/color/camera_info&#039;&#039;&lt;br /&gt;
* &#039;&#039;depth/image -&amp;gt; /camera/depth/image_rect_raw&#039;&#039;&lt;br /&gt;
* &#039;&#039;depth/camera_info -&amp;gt; /camera/depth/camera_info&#039;&#039;&lt;br /&gt;
* &#039;&#039;imu -&amp;gt; /loc/imu&#039;&#039;&lt;br /&gt;
* &#039;&#039;scan -&amp;gt; /fejemis/scan&#039;&#039;&lt;br /&gt;
* &#039;&#039;initialpose -&amp;gt; /initialpose&#039;&#039;&lt;br /&gt;
* &#039;&#039;grid_prob_map -&amp;gt; /map&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Nodes:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;depthimage_to_laserscan_node&#039;&#039; converts the depth image into &#039;&#039;/camera_scan&#039;&#039;.&lt;br /&gt;
* &#039;&#039;rtabmap_sync/rgbd_sync&#039;&#039; synchronises RGB-D data on the real robot.&lt;br /&gt;
* &#039;&#039;rtabmap_odom/icp_odometry&#039;&#039; publishes ICP odometry to &#039;&#039;/loc/rtabmap_odom&#039;&#039; on the real robot.&lt;br /&gt;
* &#039;&#039;rtabmap_slam/rtabmap&#039;&#039; runs either SLAM mode or localisation mode.&lt;br /&gt;
&lt;br /&gt;
In SLAM mode, &#039;&#039;delete_db_on_start=True&#039;&#039; and RTAB-Map is launched with &#039;&#039;-d&#039;&#039;, so the selected database is overwritten. In localisation mode, &#039;&#039;Mem/IncrementalMemory=False&#039;&#039; and &#039;&#039;Mem/InitWMWithAllNodes=True&#039;&#039;, so the existing map is loaded for localisation.&lt;br /&gt;
&lt;br /&gt;
=== launch/map.launch.py ===&lt;br /&gt;
&lt;br /&gt;
Starts a component-based Nav2 map server in namespace &#039;&#039;loc&#039;&#039;, using &#039;&#039;config/nav2_servers.yaml&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
Launch arguments:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;use_sim_time&#039;&#039; - forwarded to the map server.&lt;br /&gt;
* &#039;&#039;map_make_container&#039;&#039;, &#039;&#039;map_container&#039;&#039;, and &#039;&#039;map_container_ns&#039;&#039; - declared as container-related options. The current launch creates a container named &#039;&#039;_map&#039;&#039; with prefix/namespace &#039;&#039;map&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
The map server is configured with:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;topic_name=/loc/map&#039;&#039;&lt;br /&gt;
* &#039;&#039;frame_id=map&#039;&#039;&lt;br /&gt;
* &#039;&#039;yaml_filename=&amp;quot;&amp;quot;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
In the current system, the map is primarily produced by RTAB-Map, while the Nav2 map server exposes map-server functionality for the navigation stack.&lt;br /&gt;
&lt;br /&gt;
=== launch/planning.launch.py ===&lt;br /&gt;
&lt;br /&gt;
Starts the Nav2 planning and navigation servers, the Fejemis velocity relay, the brush service, and optionally the OpenNav coverage server.&lt;br /&gt;
&lt;br /&gt;
Launch arguments:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;namespace&#039;&#039; - namespace for Nav2 nodes. Default: &#039;&#039;fejemis&#039;&#039;.&lt;br /&gt;
* &#039;&#039;use_sim_time&#039;&#039; - forwarded to nodes. Default: &#039;&#039;False&#039;&#039;.&lt;br /&gt;
* &#039;&#039;enable_coverage&#039;&#039; - enables &#039;&#039;opennav_coverage&#039;&#039;. The default is detected from whether the coverage packages are installed.&lt;br /&gt;
&lt;br /&gt;
Nodes:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;nav2_controller/controller_server&#039;&#039; named &#039;&#039;controller&#039;&#039;.&lt;br /&gt;
* &#039;&#039;fejemis_maploc/nav2_cmd_vel_deg_relay.py&#039;&#039;.&lt;br /&gt;
* &#039;&#039;nav2_behaviors/behavior_server&#039;&#039; named &#039;&#039;behavior&#039;&#039;.&lt;br /&gt;
* &#039;&#039;nav2_planner/planner_server&#039;&#039; named &#039;&#039;planner_server&#039;&#039;.&lt;br /&gt;
* &#039;&#039;nav2_bt_navigator/bt_navigator&#039;&#039; named &#039;&#039;bt_navigator&#039;&#039;.&lt;br /&gt;
* &#039;&#039;nav2_map_server/map_server&#039;&#039;.&lt;br /&gt;
* &#039;&#039;fejemis_maploc/brush_control_service.py&#039;&#039; in namespace &#039;&#039;control/brush&#039;&#039;, named &#039;&#039;controller&#039;&#039;.&lt;br /&gt;
* &#039;&#039;opennav_coverage/opennav_coverage&#039;&#039; named &#039;&#039;coverage_server&#039;&#039;, when enabled.&lt;br /&gt;
&lt;br /&gt;
Important remappings:&lt;br /&gt;
&lt;br /&gt;
* Nav2 odometry is remapped from &#039;&#039;/behavior/odom&#039;&#039; to &#039;&#039;/loc/odom&#039;&#039;.&lt;br /&gt;
* Nav2 map input is remapped from &#039;&#039;/behavior/map&#039;&#039; to &#039;&#039;/loc/map&#039;&#039;.&lt;br /&gt;
* Nav2 controller output &#039;&#039;cmd_vel&#039;&#039; is remapped to &#039;&#039;/control/nav2/cmd_vel&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
The velocity relay then converts angular velocity units and republishes to &#039;&#039;/control/joy/cmd_vel&#039;&#039; for the existing Fejemis control path.&lt;br /&gt;
&lt;br /&gt;
The Nav2 configuration in &#039;&#039;config/nav2_servers.yaml&#039;&#039; uses:&lt;br /&gt;
&lt;br /&gt;
* Regulated Pure Pursuit as the path follower.&lt;br /&gt;
* Navfn as the global planner.&lt;br /&gt;
* Local costmap sources from LiDAR, camera scan, net scan, and cable scan.&lt;br /&gt;
* Global costmap with static map, LiDAR obstacle layer, and inflation layer.&lt;br /&gt;
* The OpenNav coverage navigator when coverage support is enabled.&lt;br /&gt;
&lt;br /&gt;
=== launch/lifecycle.launch.py ===&lt;br /&gt;
&lt;br /&gt;
Starts Nav2 lifecycle managers.&lt;br /&gt;
&lt;br /&gt;
Launch arguments:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;in_sim&#039;&#039; - forwarded as &#039;&#039;use_sim_time&#039;&#039;.&lt;br /&gt;
* &#039;&#039;enable_loc_lfc&#039;&#039; - controls whether the localisation lifecycle manager is started. Default: &#039;&#039;false&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
Lifecycle managers:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;/fejemis/beh_lfc&#039;&#039; - behaviour/navigation lifecycle manager.&lt;br /&gt;
* &#039;&#039;/loc/loc_lfc&#039;&#039; - localisation lifecycle manager, only when &#039;&#039;enable_loc_lfc=true&#039;&#039;.&lt;br /&gt;
* &#039;&#039;/fejemis/hard_lfc&#039;&#039; - hardware lifecycle manager, disabled in simulation.&lt;br /&gt;
&lt;br /&gt;
Diagnostics are remapped to &#039;&#039;/diag/beh_lfc&#039;&#039;, &#039;&#039;/diag/loc_lfc&#039;&#039;, and &#039;&#039;/diag/hard_lfc&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
=== launch/lifecycle_managers_only.launch.py ===&lt;br /&gt;
&lt;br /&gt;
Starts the navigation-side launch files without the heavy sensor components. This is useful when another machine is responsible for LiDAR, RealSense, or RTAB-Map.&lt;br /&gt;
&lt;br /&gt;
Launch arguments:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;in_sim&#039;&#039; - enables simulation time.&lt;br /&gt;
* &#039;&#039;localization&#039;&#039; - when true, starts the planning stack on the main Pi. Default: &#039;&#039;false&#039;&#039;.&lt;br /&gt;
* &#039;&#039;enable_coverage&#039;&#039; - enables coverage navigation. Default: &#039;&#039;true&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
Included launch files:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;planning.launch.py&#039;&#039; is included only when &#039;&#039;in_sim=true&#039;&#039; or &#039;&#039;localization=true&#039;&#039;.&lt;br /&gt;
* &#039;&#039;lifecycle.launch.py&#039;&#039; is included immediately.&lt;br /&gt;
* &#039;&#039;behavior.launch.py&#039;&#039; is included after a 30 second delay, giving Nav2 servers time to become active before the BT engine connects to action servers.&lt;br /&gt;
&lt;br /&gt;
=== launch/behavior.launch.py ===&lt;br /&gt;
&lt;br /&gt;
Starts the Fejemis behaviour tree engine from &#039;&#039;raubase_core&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
Launch argument:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;use_sim_time&#039;&#039; - forwarded to the BT engine.&lt;br /&gt;
&lt;br /&gt;
The launch file selects the behaviour tree XML based on the ROS distribution:&lt;br /&gt;
&lt;br /&gt;
* Older BehaviourTree.CPP format: &#039;&#039;resources/behavior/main.xml&#039;&#039;.&lt;br /&gt;
* BehaviourTree.CPP v4 format: &#039;&#039;resources/behavior/main4.xml&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
The node launched is:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;raubase_core/bt_engine&#039;&#039;, configured by &#039;&#039;config/bt_engine.yaml&#039;&#039; in namespace &#039;&#039;fejemis&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
The BT plugin configuration loads:&lt;br /&gt;
&lt;br /&gt;
* raubase logging, mission, blackboard, coverage, and mixer plugins.&lt;br /&gt;
* Nav2 action plugins for single trigger, follow path, drive on heading, spin, and navigate to pose.&lt;br /&gt;
* OpenNav coverage path computation plugin.&lt;br /&gt;
* The package&#039;s custom &#039;&#039;fejemis_call_trigger_service_bt_node&#039;&#039; plugin.&lt;br /&gt;
&lt;br /&gt;
=== launch/robot.launch.py inclusion ===&lt;br /&gt;
&lt;br /&gt;
The top-level &#039;&#039;fejemis&#039;&#039; package includes &#039;&#039;fejemis_maploc&#039;&#039; from &#039;&#039;fejemis/launch/robot.launch.py&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
Typical distribution:&lt;br /&gt;
&lt;br /&gt;
* Main Pi: bridge control, odometry, lifecycle managers, behaviour tree, and planning when localisation is enabled.&lt;br /&gt;
* Auxiliary Pi: LiDAR, RTAB-Map, RealSense, and optional vision.&lt;br /&gt;
&lt;br /&gt;
The top-level launch waits 3 seconds after bridge startup before launching the rest of the stack.&lt;br /&gt;
&lt;br /&gt;
== Script Nodes ==&lt;br /&gt;
&lt;br /&gt;
=== nav2_cmd_vel_deg_relay.py ===&lt;br /&gt;
&lt;br /&gt;
Purpose: adapts Nav2 velocity commands to the Fejemis control convention.&lt;br /&gt;
&lt;br /&gt;
Subscriptions:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;/control/nav2/cmd_vel&#039;&#039; - &#039;&#039;geometry_msgs/Twist&#039;&#039;, with angular velocity in rad/s.&lt;br /&gt;
&lt;br /&gt;
Publications:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;/control/joy/cmd_vel&#039;&#039; - &#039;&#039;geometry_msgs/Twist&#039;&#039;, with angular velocity converted to deg/s.&lt;br /&gt;
&lt;br /&gt;
The linear fields are copied unchanged. The angular x, y, and z fields are multiplied by 180/pi. This is needed because Nav2 uses the normal ROS convention of rad/s, while the existing Fejemis bridge control path expects angular velocity in deg/s.&lt;br /&gt;
&lt;br /&gt;
=== brush_control_service.py ===&lt;br /&gt;
&lt;br /&gt;
Purpose: exposes simple ROS services for turning the brush on and off.&lt;br /&gt;
&lt;br /&gt;
Parameters:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;manage_topic&#039;&#039; - topic for &#039;&#039;fejemis_bridge/msg/SteerManage&#039;&#039;. Default: &#039;&#039;/fejemis/front/manage&#039;&#039;.&lt;br /&gt;
* &#039;&#039;qos_depth&#039;&#039; - publisher QoS depth. Default: &#039;&#039;10&#039;&#039;.&lt;br /&gt;
* &#039;&#039;initial_brush_on&#039;&#039; - initial internal state. Default: &#039;&#039;False&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
Publications:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;manage_topic&#039;&#039; - publishes &#039;&#039;SteerManage.BRUSH_ON&#039;&#039; or &#039;&#039;SteerManage.BRUSH_OFF&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
Services:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;~/set&#039;&#039; - &#039;&#039;std_srvs/SetBool&#039;&#039;, true means brush on and false means brush off.&lt;br /&gt;
* &#039;&#039;~/on&#039;&#039; - &#039;&#039;std_srvs/Trigger&#039;&#039;, turns the brush on.&lt;br /&gt;
* &#039;&#039;~/off&#039;&#039; - &#039;&#039;std_srvs/Trigger&#039;&#039;, turns the brush off.&lt;br /&gt;
&lt;br /&gt;
In &#039;&#039;planning.launch.py&#039;&#039; this node runs as &#039;&#039;/control/brush/controller&#039;&#039;, which creates the services used by the behaviour tree:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;/control/brush/controller/on&#039;&#039;&lt;br /&gt;
* &#039;&#039;/control/brush/controller/off&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
=== rviz_goal_relay.py ===&lt;br /&gt;
&lt;br /&gt;
Purpose: lets an operator send a Nav2 goal from RViz.&lt;br /&gt;
&lt;br /&gt;
Subscriptions:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;/goal_pose&#039;&#039; - RViz 2D Nav Goal as &#039;&#039;geometry_msgs/PoseStamped&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
Action clients:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;/fejemis/navigate_to_pose&#039;&#039; - &#039;&#039;nav2_msgs/action/NavigateToPose&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
When a goal is clicked in RViz, the node checks that the action server is ready, forwards the goal, and logs whether it was accepted and whether it succeeded.&lt;br /&gt;
&lt;br /&gt;
=== mission_trigger.py ===&lt;br /&gt;
&lt;br /&gt;
Purpose: starts a cleaning mission by publishing blackboard values and then setting the active mission.&lt;br /&gt;
&lt;br /&gt;
Parameters:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;mission&#039;&#039; - mission string to publish. Default: &#039;&#039;cleaning/waypoint&#039;&#039;.&lt;br /&gt;
* &#039;&#039;dock_pose&#039;&#039; - dock pose as &#039;&#039;[x, y, yaw]&#039;&#039;. Default: &#039;&#039;[2.62, 3.9, 0.0]&#039;&#039;.&lt;br /&gt;
* &#039;&#039;waypoint_pose&#039;&#039; - waypoint pose as &#039;&#039;[x, y, yaw]&#039;&#039;. Default: &#039;&#039;[2.10491, 0.497438, 3.11127]&#039;&#039;.&lt;br /&gt;
* &#039;&#039;clean_poly&#039;&#039; - polygon string used for cleaning area blackboard values.&lt;br /&gt;
* &#039;&#039;startup_delay&#039;&#039; - delay before publishing. Default: &#039;&#039;2.0&#039;&#039; seconds.&lt;br /&gt;
&lt;br /&gt;
Publications:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;/blackboard/dock_x&#039;&#039;, &#039;&#039;/blackboard/dock_y&#039;&#039;, &#039;&#039;/blackboard/dock_yaw&#039;&#039;&lt;br /&gt;
* &#039;&#039;/blackboard/waypoint_x&#039;&#039;, &#039;&#039;/blackboard/waypoint_y&#039;&#039;, &#039;&#039;/blackboard/waypoint_yaw&#039;&#039;&lt;br /&gt;
* &#039;&#039;/blackboard/field_polygon&#039;&#039;&lt;br /&gt;
* &#039;&#039;/blackboard/clean_poly&#039;&#039;&lt;br /&gt;
* &#039;&#039;/blackboard/start_pose&#039;&#039;&lt;br /&gt;
* &#039;&#039;/blackboard/dock_pose&#039;&#039;&lt;br /&gt;
* &#039;&#039;/blackboard/waypoint_pose&#039;&#039;&lt;br /&gt;
* &#039;&#039;/behavior/set_mission&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The blackboard publishers use transient-local QoS so late subscribers can still receive the latest values. Pose strings are encoded as:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
stamp;frame;x;y;z;qx;qy;qz;qw&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The node also subscribes to &#039;&#039;/behavior/set_mission&#039;&#039;. When it sees &#039;&#039;auto/idle&#039;&#039; after the mission has run, it logs completion and shuts down.&lt;br /&gt;
&lt;br /&gt;
=== complete_coverage_planner.py ===&lt;br /&gt;
&lt;br /&gt;
Purpose: interactive coverage test node for RViz.&lt;br /&gt;
&lt;br /&gt;
Parameters:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;rviz_click_topic&#039;&#039; - clicked point topic. Default: &#039;&#039;/clicked_point&#039;&#039;.&lt;br /&gt;
* &#039;&#039;coverage_frame&#039;&#039; - frame for the coverage polygon. Default: &#039;&#039;map&#039;&#039;.&lt;br /&gt;
* &#039;&#039;min_polygon_points&#039;&#039; - number of clicked points before sending a goal. Default: &#039;&#039;4&#039;&#039;.&lt;br /&gt;
* &#039;&#039;auto_close_polygon&#039;&#039; - whether to append the first point as the final point. Default: &#039;&#039;True&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
Subscriptions:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;/clicked_point&#039;&#039; by default - RViz point clicks.&lt;br /&gt;
&lt;br /&gt;
Action clients:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;fejemis/navigate_complete_coverage&#039;&#039; - &#039;&#039;opennav_coverage_msgs/action/NavigateCompleteCoverage&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
The node waits for &#039;&#039;fejemis/bt_navigator&#039;&#039; to become active, collects clicked points in RViz, converts them to a &#039;&#039;geometry_msgs/Polygon&#039;&#039;, and sends a coverage navigation action once enough points have been collected.&lt;br /&gt;
&lt;br /&gt;
=== fixed_complete_coverage_planner.py ===&lt;br /&gt;
&lt;br /&gt;
Purpose: fixed-polygon coverage test node.&lt;br /&gt;
&lt;br /&gt;
Parameters:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;coverage_frame&#039;&#039; - frame for the coverage polygon. Default: &#039;&#039;map&#039;&#039;.&lt;br /&gt;
* &#039;&#039;min_polygon_points&#039;&#039; - retained from the interactive tester. Default: &#039;&#039;4&#039;&#039;.&lt;br /&gt;
* &#039;&#039;auto_close_polygon&#039;&#039; - whether to append the first point as the final point. Default: &#039;&#039;True&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
Action clients:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;fejemis/navigate_complete_coverage&#039;&#039; - &#039;&#039;opennav_coverage_msgs/action/NavigateCompleteCoverage&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
The node waits for &#039;&#039;fejemis/bt_navigator&#039;&#039; to become active, then sends a fixed four-point polygon:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
(-5.35,  2.45)&lt;br /&gt;
( 2.83,  3.18)&lt;br /&gt;
( 3.22, -5.08)&lt;br /&gt;
(-4.76, -5.97)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This is useful for testing coverage planning without clicking points manually in RViz.&lt;br /&gt;
&lt;br /&gt;
== C++ Behaviour Tree Plugin ==&lt;br /&gt;
&lt;br /&gt;
=== fejemis_call_trigger_service_bt_node ===&lt;br /&gt;
&lt;br /&gt;
The C++ plugin &#039;&#039;src/bt_plugins/call_trigger_service_bt_node.cpp&#039;&#039; registers the behaviour tree node &#039;&#039;CallTriggerService&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
Inputs:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;service_name&#039;&#039; - service to call.&lt;br /&gt;
* &#039;&#039;wait_for_service_ms&#039;&#039; - timeout while waiting for service availability. Default: &#039;&#039;1000&#039;&#039;.&lt;br /&gt;
* &#039;&#039;call_timeout_ms&#039;&#039; - timeout for the service call. Default: &#039;&#039;2000&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
The node creates a &#039;&#039;std_srvs/Trigger&#039;&#039; client, waits for the target service, calls it, and returns:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;SUCCESS&#039;&#039; when the service exists, responds before timeout, and returns &#039;&#039;success=true&#039;&#039;.&lt;br /&gt;
* &#039;&#039;FAILURE&#039;&#039; when the service is unavailable, the call times out, or the response reports failure.&lt;br /&gt;
&lt;br /&gt;
The cleaning subtree uses it to call:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;/control/brush/controller/on&#039;&#039;&lt;br /&gt;
* &#039;&#039;/control/brush/controller/off&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
== Behaviour Tree ==&lt;br /&gt;
&lt;br /&gt;
The active behaviour tree on newer ROS distributions is &#039;&#039;resources/behavior/main4.xml&#039;&#039;. It is loaded by &#039;&#039;behavior.launch.py&#039;&#039; through the &#039;&#039;raubase_core/bt_engine&#039;&#039; node.&lt;br /&gt;
&lt;br /&gt;
=== Main Loop ===&lt;br /&gt;
&lt;br /&gt;
The root tree is &#039;&#039;main&#039;&#039;:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
main&lt;br /&gt;
`-- Sequence&lt;br /&gt;
    |-- SingleTrigger&lt;br /&gt;
    |   `-- t_init&lt;br /&gt;
    `-- KeepRunningUntilFailure&lt;br /&gt;
        `-- ReactiveSequence&lt;br /&gt;
            |-- ForceSuccess&lt;br /&gt;
            |   `-- t_passive&lt;br /&gt;
            `-- t_runtime&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The structure means:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;t_init&#039;&#039; runs once at startup.&lt;br /&gt;
* &#039;&#039;t_passive&#039;&#039; runs repeatedly and updates blackboard/mission state.&lt;br /&gt;
* &#039;&#039;t_runtime&#039;&#039; selects the active behaviour based on the current mission.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Visualization placeholder:&#039;&#039;&#039; exported Groot image of &#039;&#039;main4.xml&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
=== t_init ===&lt;br /&gt;
&lt;br /&gt;
Initialises the robot behaviour state:&lt;br /&gt;
&lt;br /&gt;
* Sets mission to &#039;&#039;auto/idle&#039;&#039;.&lt;br /&gt;
* Sets blackboard key &#039;&#039;initialized=1&#039;&#039;.&lt;br /&gt;
* Sets calibration square distance &#039;&#039;calib_sq_dist=7&#039;&#039;.&lt;br /&gt;
* Opens the mixer port using &#039;&#039;OpenMixerPort&#039;&#039; with priority 4 and topic &#039;&#039;/behavior&#039;&#039;.&lt;br /&gt;
* Logs that the robot is initialized.&lt;br /&gt;
&lt;br /&gt;
=== t_passive ===&lt;br /&gt;
&lt;br /&gt;
Runs as the passive safety/update loop:&lt;br /&gt;
&lt;br /&gt;
* Logs &#039;&#039;Safety loop&#039;&#039; at debug level.&lt;br /&gt;
* Refreshes &#039;&#039;start_pose&#039;&#039;, &#039;&#039;waypoint_pose&#039;&#039;, &#039;&#039;clean_poly&#039;&#039;, and &#039;&#039;field_polygon&#039;&#039; from &#039;&#039;/blackboard/...&#039;&#039; topics.&lt;br /&gt;
* Refreshes the current mission from &#039;&#039;/behavior/set_mission&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
=== t_runtime ===&lt;br /&gt;
&lt;br /&gt;
Selects behaviour using a reactive fallback:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
t_runtime&lt;br /&gt;
|-- t_idle_runtime&lt;br /&gt;
|-- t_calib&lt;br /&gt;
|-- t_mapping&lt;br /&gt;
|-- t_cleaning&lt;br /&gt;
`-- unknown mission handler -&amp;gt; set idle&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The first matching mission branch runs. If no known branch matches, the tree logs an error and returns to idle.&lt;br /&gt;
&lt;br /&gt;
=== Idle ===&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;t_idle_runtime&#039;&#039; succeeds when the current mission is:&lt;br /&gt;
&lt;br /&gt;
* mission: &#039;&#039;auto&#039;&#039;&lt;br /&gt;
* mission_sub: &#039;&#039;idle&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
This keeps the robot in a waiting state. Sensors, telemetry, and subscriptions remain active, but no movement commands are issued. The robot leaves this state when a new mission is received through &#039;&#039;/behavior/set_mission&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
=== Calibration ===&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;t_calib&#039;&#039; chooses between line calibration and square calibration.&lt;br /&gt;
&lt;br /&gt;
Line calibration, &#039;&#039;t_calib_line&#039;&#039;:&lt;br /&gt;
&lt;br /&gt;
* Requires mission &#039;&#039;calib/line&#039;&#039;.&lt;br /&gt;
* Drives forward using &#039;&#039;DriveOnHeading&#039;&#039; &lt;br /&gt;
* &#039;&#039;calib_sq_dist&#039;&#039; is set to 0.05 m/s during initialization.&lt;br /&gt;
* Returns to idle.&lt;br /&gt;
&lt;br /&gt;
Square calibration, &#039;&#039;t_calib_square&#039;&#039;:&lt;br /&gt;
&lt;br /&gt;
* Requires mission &#039;&#039;calib/*&#039;&#039;.&lt;br /&gt;
* Runs &#039;&#039;f_calib_side&#039;&#039; four times.&lt;br /&gt;
* Each side drives forward and spins 1.57 rad.&lt;br /&gt;
* Returns to idle.&lt;br /&gt;
Each &#039;&#039;f_calib_side&#039;&#039; contains:&lt;br /&gt;
&lt;br /&gt;
*DriveOnHeading&lt;br /&gt;
*Spin&lt;br /&gt;
*A quarter-turn style calibration step&lt;br /&gt;
Calibration succeeds when each action in the sequence completes successfully.&lt;br /&gt;
&lt;br /&gt;
=== Mapping ===&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;t_mapping&#039;&#039; currently checks for &#039;&#039;mission mapping/*&#039;&#039; and then returns to idle.&lt;br /&gt;
&lt;br /&gt;
*Requires mission mapping/*&lt;br /&gt;
&lt;br /&gt;
*Currently acts as a placeholder&lt;br /&gt;
&lt;br /&gt;
*Does not yet launch the SLAM or exploration pipeline&lt;br /&gt;
&lt;br /&gt;
*Returns to idle immediately&lt;br /&gt;
&lt;br /&gt;
This branch should later be expanded to perform autonomous mapping, localization checks, and map saving.&lt;br /&gt;
&lt;br /&gt;
=== Cleaning ===&lt;br /&gt;
&lt;br /&gt;
The current cleaning branch is &#039;&#039;t_cleaning&#039;&#039;:&lt;br /&gt;
&lt;br /&gt;
* Requires mission &#039;&#039;cleaning/waypoint&#039;&#039; with mission_sub &#039;&#039;idle&#039;&#039;.&lt;br /&gt;
* Navigates to &#039;&#039;{waypoint_pose}&#039;&#039; using &#039;&#039;NavigateToPose&#039;&#039;.&lt;br /&gt;
* Sets current mission to &#039;&#039;cleaning/area&#039;&#039;.&lt;br /&gt;
* Returns to idle.&lt;br /&gt;
&lt;br /&gt;
Supporting cleaning subtrees are already present:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;f_get_area_to_clean&#039;&#039; sets a polygon and calls &#039;&#039;ComputeCoveragePath&#039;&#039;.&lt;br /&gt;
* &#039;&#039;f_clean_area&#039;&#039; turns the brush on, follows &#039;&#039;{cover_nav_path}&#039;&#039;, and turns the brush off.&lt;br /&gt;
* &#039;&#039;f_go_to_path_start&#039;&#039; is a placeholder that logs navigation to the path start.&lt;br /&gt;
&lt;br /&gt;
These subtrees show the intended full cleaning sequence, although the active &#039;&#039;t_cleaning&#039;&#039; branch currently only navigates to a waypoint and changes mission state.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Diagram placeholder:&#039;&#039;&#039; cleaning behaviour sequence: mission trigger -&amp;gt; waypoint navigation -&amp;gt; coverage path computation -&amp;gt; brush on -&amp;gt; follow coverage path -&amp;gt; brush off -&amp;gt; idle.&lt;br /&gt;
&lt;br /&gt;
== Configuration Files ==&lt;br /&gt;
&lt;br /&gt;
=== config/odometry.yaml and config/odometry_sim.yaml ===&lt;br /&gt;
&lt;br /&gt;
Configure the EKF and IMU filter. The real configuration fuses wheel odometry and RTAB-Map ICP odometry in 2D mode. The simulation configuration is separated to avoid conflicting simulated IMU orientation.&lt;br /&gt;
&lt;br /&gt;
=== config/rtabmap.yaml and config/rtabmap_sim.yaml ===&lt;br /&gt;
&lt;br /&gt;
Configure RTAB-Map for real robot and simulation use. &#039;&#039;rtabmap_slam.launch.py&#039;&#039; selects between them using &#039;&#039;use_sim_time&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
=== config/nav2_servers.yaml and config/nav2_sim.yaml ===&lt;br /&gt;
&lt;br /&gt;
Configure Nav2 controller, planner, behaviour server, costmaps, BT navigator, coverage server, and map server. The main real-robot launch uses &#039;&#039;nav2_servers.yaml&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
=== config/bt_engine.yaml ===&lt;br /&gt;
&lt;br /&gt;
Lists behaviour tree plugins loaded by &#039;&#039;raubase_core/bt_engine&#039;&#039;, including the Fejemis custom trigger-service plugin.&lt;br /&gt;
&lt;br /&gt;
=== config/lifecycle.yaml ===&lt;br /&gt;
&lt;br /&gt;
Configures lifecycle manager node lists and autostart behaviour for the navigation, localisation, and hardware lifecycle managers.&lt;br /&gt;
&lt;br /&gt;
=== config/realsense.yaml ===&lt;br /&gt;
&lt;br /&gt;
RealSense camera configuration loaded by &#039;&#039;realsense.launch.py&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
== Typical Usage ==&lt;br /&gt;
&lt;br /&gt;
Start the complete robot stack through the top-level launch:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ros2 launch fejemis robot.launch.py localization:=true map_location:=asta&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Run on both Raspberry Pis. The launch file decides which parts run on the main and auxiliary computers through the &#039;&#039;is_aux&#039;&#039; argument.&lt;br /&gt;
&lt;br /&gt;
Start a waypoint cleaning mission after the system is up:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ros2 run fejemis_maploc mission_trigger.py&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Send an RViz navigation goal:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ros2 run fejemis_maploc rviz_goal_relay.py&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Test coverage navigation with RViz clicked points:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ros2 run fejemis_maploc complete_coverage_planner.py&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Test coverage navigation with the fixed polygon:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ros2 run fejemis_maploc fixed_complete_coverage_planner.py&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Notes and Future Work ==&lt;br /&gt;
&lt;br /&gt;
* Clarify which Raspberry Pi should run each launch file in the final deployment.&lt;br /&gt;
* Expand the cleaning behaviour section once &#039;&#039;t_cleaning&#039;&#039; is connected to &#039;&#039;f_get_area_to_clean&#039;&#039; and &#039;&#039;f_clean_area&#039;&#039; in the active tree.&lt;/div&gt;</summary>
		<author><name>S252724</name></author>
	</entry>
	<entry>
		<id>https://rsewiki.electro.dtu.dk/index.php?title=Fejemis_Maploc_Ros2&amp;diff=8967</id>
		<title>Fejemis Maploc Ros2</title>
		<link rel="alternate" type="text/html" href="https://rsewiki.electro.dtu.dk/index.php?title=Fejemis_Maploc_Ros2&amp;diff=8967"/>
		<updated>2026-05-28T10:11:17Z</updated>

		<summary type="html">&lt;p&gt;S252724: /* Calibration */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Back to [[Fejemis ROS2 Software]]&lt;br /&gt;
&lt;br /&gt;
== Purpose ==&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fejemis_maploc&#039;&#039; is the mapping, localisation, navigation, and behaviour package for Fejemis. It connects the robot&#039;s sensor data to the navigation stack and exposes the high-level behaviour tree used to run calibration, mapping, waypoint navigation, and cleaning missions.&lt;br /&gt;
&lt;br /&gt;
The package sits between the low-level bridge and the high-level mission logic:&lt;br /&gt;
&lt;br /&gt;
* The bridge publishes wheel odometry, IMU data, and accepts velocity / brush commands.&lt;br /&gt;
* &#039;&#039;fejemis_maploc&#039;&#039; filters odometry, starts LiDAR and RealSense sensing, runs RTAB-Map, starts Nav2, and runs the Fejemis behaviour tree.&lt;br /&gt;
* The behaviour tree sends commands to Nav2, the coverage planner, the mixer, and the brush service.&lt;br /&gt;
[[File:maploc_general_block_diagram.png| 1600px]]&lt;br /&gt;
&lt;br /&gt;
Overall &#039;&#039;fejemis_maploc&#039;&#039; data flow from bridge, LiDAR, and RealSense into odometry, RTAB-Map, Nav2, coverage planning, behaviour tree, and robot commands.&lt;br /&gt;
&lt;br /&gt;
== Package Layout ==&lt;br /&gt;
&lt;br /&gt;
Important package folders:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;resources/launch&#039;&#039; - ROS 2 launch files for sensors, odometry, SLAM/localisation, planning, lifecycle managers, and behaviour trees.&lt;br /&gt;
* &#039;&#039;resources/config&#039;&#039; - YAML configuration for odometry, Nav2, RTAB-Map, RealSense, lifecycle managers, and BT plugins.&lt;br /&gt;
* &#039;&#039;resources/behavior&#039;&#039; - behaviour tree XML files. &#039;&#039;main.xml&#039;&#039; is the older BT.CPP format and &#039;&#039;main4.xml&#039;&#039; is the BT.CPP v4 version used on newer ROS distributions.&lt;br /&gt;
* &#039;&#039;scripts&#039;&#039; - Python helper nodes for mission triggering, RViz goal relays, brush services, velocity conversion, and coverage tests.&lt;br /&gt;
* &#039;&#039;src/bt_plugins&#039;&#039; - C++ behaviour tree plugin used by the cleaning behaviour.&lt;br /&gt;
&lt;br /&gt;
== Main Topics and Frames ==&lt;br /&gt;
&lt;br /&gt;
Common frame names:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;map&#039;&#039; - global map frame produced by RTAB-Map / Nav2.&lt;br /&gt;
* &#039;&#039;odom&#039;&#039; - local odometry frame.&lt;br /&gt;
* &#039;&#039;base_link&#039;&#039; - robot base frame.&lt;br /&gt;
* &#039;&#039;lidar_link&#039;&#039; - LiDAR frame.&lt;br /&gt;
* &#039;&#039;camera_camera_link&#039;&#039; and RealSense optical frames - camera frames used by the RealSense driver and RTAB-Map.&lt;br /&gt;
&lt;br /&gt;
Important topics and actions:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;/fejemis/odom&#039;&#039; - wheel odometry from the bridge.&lt;br /&gt;
* &#039;&#039;/fejemis/imu&#039;&#039; and &#039;&#039;/fejemis/mag&#039;&#039; - raw IMU and magnetometer inputs.&lt;br /&gt;
* &#039;&#039;/loc/imu&#039;&#039; - filtered IMU output from Madgwick.&lt;br /&gt;
* &#039;&#039;/loc/odom&#039;&#039; - fused odometry output from the EKF.&lt;br /&gt;
* &#039;&#039;/fejemis/scan&#039;&#039; - LiDAR scan.&lt;br /&gt;
* &#039;&#039;/camera/color/image_raw&#039;&#039;, &#039;&#039;/camera/depth/image_rect_raw&#039;&#039;, and camera info topics - RealSense RGB-D input.&lt;br /&gt;
* &#039;&#039;/camera_scan&#039;&#039; - depth-image-to-laserscan output used as an extra obstacle source.&lt;br /&gt;
* &#039;&#039;/loc/map&#039;&#039; - occupancy map used by Nav2.&lt;br /&gt;
* &#039;&#039;/control/nav2/cmd_vel&#039;&#039; - Nav2 velocity command before Fejemis-specific conversion.&lt;br /&gt;
* &#039;&#039;/control/joy/cmd_vel&#039;&#039; - velocity command sent into the existing Fejemis control path.&lt;br /&gt;
* &#039;&#039;/behavior/set_mission&#039;&#039; - mission command topic consumed by the behaviour tree.&lt;br /&gt;
* &#039;&#039;/blackboard/...&#039;&#039; - blackboard values mirrored into the behaviour tree.&lt;br /&gt;
* &#039;&#039;/fejemis/navigate_to_pose&#039;&#039; - Nav2 action used for waypoint goals.&lt;br /&gt;
* &#039;&#039;/fejemis/navigate_complete_coverage&#039;&#039; - OpenNav coverage action used for coverage missions.&lt;br /&gt;
* &#039;&#039;/control/brush/controller/on&#039;&#039; and &#039;&#039;/control/brush/controller/off&#039;&#039; - brush control trigger services.&lt;br /&gt;
&lt;br /&gt;
== Launch Files ==&lt;br /&gt;
&lt;br /&gt;
The package is designed so the top-level &#039;&#039;fejemis/launch/robot.launch.py&#039;&#039; can include only the parts needed on each Raspberry Pi. The main Pi typically handles bridge control and odometry, while the auxiliary Pi handles heavier sensing such as LiDAR, RealSense, RTAB-Map, and vision.&lt;br /&gt;
&lt;br /&gt;
=== launch/lidar.launch.py ===&lt;br /&gt;
&lt;br /&gt;
Starts the LD19 LiDAR driver from &#039;&#039;ldlidar_stl_ros2&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
Launch arguments:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;ld_make_container&#039;&#039; - whether the LiDAR launch should create a component container. Default: &#039;&#039;True&#039;&#039;.&lt;br /&gt;
* &#039;&#039;ld_container&#039;&#039; - component container name. Default: &#039;&#039;_laser&#039;&#039;.&lt;br /&gt;
* &#039;&#039;ld_container_ns&#039;&#039; - namespace for the component container. Default: &#039;&#039;loc&#039;&#039;.&lt;br /&gt;
* &#039;&#039;port_name&#039;&#039; - serial port for the LiDAR. Default: &#039;&#039;/dev/ttyUSB0&#039;&#039;.&lt;br /&gt;
* &#039;&#039;namespace&#039;&#039; - launch argument declared for compatibility. The included launch currently receives &#039;&#039;fejemis&#039;&#039; as namespace.&lt;br /&gt;
&lt;br /&gt;
Important configuration passed to the driver:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;laser_frame=lidar_link&#039;&#039;&lt;br /&gt;
* &#039;&#039;clockwise_dir=false&#039;&#039;&lt;br /&gt;
* &#039;&#039;range_min=0.2&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The resulting scan is used by RTAB-Map and by the Nav2 costmaps as the main 2D obstacle source.&lt;br /&gt;
&lt;br /&gt;
=== launch/tf.launch.py ===&lt;br /&gt;
&lt;br /&gt;
Publishes static transforms for the physical sensor layout.&lt;br /&gt;
&lt;br /&gt;
Published transforms:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;base_link -&amp;gt; camera_camera_link&#039;&#039; at approximately x=-0.24 m, y=0.0 m, z=0.44 m.&lt;br /&gt;
* On the real robot, &#039;&#039;base_link -&amp;gt; lidar_link&#039;&#039; at approximately x=-0.29 m, y=0.0 m, z=0.25 m, yaw=-1.5708 rad. The yaw correction compensates for the LiDAR&#039;s physical mounting angle.&lt;br /&gt;
* In simulation, &#039;&#039;laser_frame -&amp;gt; lidar_link&#039;&#039; is published instead, because Gazebo already provides &#039;&#039;laser_frame&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
The launch file uses the &#039;&#039;in_sim&#039;&#039; launch configuration to select the real or simulated LiDAR transform.&lt;br /&gt;
&lt;br /&gt;
=== launch/odometry.launch.py ===&lt;br /&gt;
&lt;br /&gt;
Starts the odometry estimation path. On the real robot it runs both the Madgwick IMU filter and the robot_localization EKF. In simulation it only runs the EKF with the simulation-specific configuration.&lt;br /&gt;
&lt;br /&gt;
Launch argument:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;use_sim_time&#039;&#039; - selects real or simulated odometry configuration. Default: &#039;&#039;False&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
Real robot nodes:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;imu_filter_madgwick_node&#039;&#039; named &#039;&#039;imu_filter&#039;&#039; in namespace &#039;&#039;loc&#039;&#039;.&lt;br /&gt;
* &#039;&#039;robot_localization/ekf_node&#039;&#039; named &#039;&#039;ekf&#039;&#039; in namespace &#039;&#039;loc&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
Real robot remappings:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;imu/mag -&amp;gt; /fejemis/mag&#039;&#039;&lt;br /&gt;
* &#039;&#039;imu/data_raw -&amp;gt; /fejemis/imu&#039;&#039;&lt;br /&gt;
* &#039;&#039;imu/data -&amp;gt; imu&#039;&#039;, which resolves to &#039;&#039;/loc/imu&#039;&#039;.&lt;br /&gt;
* &#039;&#039;/loc/odom_in -&amp;gt; /fejemis/odom&#039;&#039;&lt;br /&gt;
* &#039;&#039;odometry/filtered -&amp;gt; odom&#039;&#039;, which resolves to &#039;&#039;/loc/odom&#039;&#039;.&lt;br /&gt;
* diagnostics are remapped to &#039;&#039;/loc/ekf/_diagnostics&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
The EKF configuration in &#039;&#039;config/odometry.yaml&#039;&#039; runs at 30 Hz, uses two-dimensional mode, publishes TF, and fuses:&lt;br /&gt;
&lt;br /&gt;
* Wheel odometry from &#039;&#039;/fejemis/odom&#039;&#039;.&lt;br /&gt;
* RTAB-Map ICP odometry from &#039;&#039;/loc/rtabmap_odom&#039;&#039;.&lt;br /&gt;
* Filtered IMU input is present as &#039;&#039;/loc/imu&#039;&#039;, although the current real configuration disables the IMU state variables in the EKF and relies more heavily on odometry sources for motion estimation.&lt;br /&gt;
&lt;br /&gt;
Simulation mode uses &#039;&#039;config/odometry_sim.yaml&#039;&#039; and avoids the Madgwick IMU orientation because the simulated IMU orientation can conflict with Gazebo odometry frame conventions.&lt;br /&gt;
&lt;br /&gt;
[[File:fejemis_maploc_ekf_block_diagram.png| 800px]]&lt;br /&gt;
&lt;br /&gt;
EKF input/output diagram: &#039;&#039;/fejemis/odom&#039;&#039;, &#039;&#039;/loc/rtabmap_odom&#039;&#039;, &#039;&#039;/loc/imu&#039;&#039; into &#039;&#039;/loc/ekf&#039;&#039;, output &#039;&#039;/loc/odom&#039;&#039; and TF.&lt;br /&gt;
&lt;br /&gt;
=== launch/realsense.launch.py ===&lt;br /&gt;
&lt;br /&gt;
Includes the official &#039;&#039;realsense2_camera&#039;&#039; launch file with the package&#039;s &#039;&#039;config/realsense.yaml&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
Launch arguments:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;camera_namespace&#039;&#039; - namespace for the camera. Default: empty string.&lt;br /&gt;
* &#039;&#039;use_sim_time&#039;&#039; - declared for consistency, although the included launch is mainly configured through the RealSense launch arguments.&lt;br /&gt;
&lt;br /&gt;
Important RealSense settings:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;publish_tf=true&#039;&#039;&lt;br /&gt;
* &#039;&#039;enable_sync=true&#039;&#039;&lt;br /&gt;
* &#039;&#039;unite_imu_method=2&#039;&#039;&lt;br /&gt;
* &#039;&#039;config_file=fejemis_maploc/config/realsense.yaml&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The camera publishes the RGB, depth, camera info, IMU, and TF data used by RTAB-Map and by the depth-to-scan obstacle conversion.&lt;br /&gt;
&lt;br /&gt;
=== launch/rtabmap_slam.launch.py ===&lt;br /&gt;
&lt;br /&gt;
Starts the RTAB-Map mapping/localisation pipeline and includes &#039;&#039;tf.launch.py&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
Launch arguments:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;use_sim_time&#039;&#039; - selects real or simulation config. Default: &#039;&#039;false&#039;&#039;.&lt;br /&gt;
* &#039;&#039;localization&#039;&#039; - if &#039;&#039;true&#039;&#039;, RTAB-Map loads an existing database and does not increment the map. If &#039;&#039;false&#039;&#039;, it starts in mapping mode and deletes the database on start. Default: &#039;&#039;false&#039;&#039;.&lt;br /&gt;
* &#039;&#039;namespace&#039;&#039; - namespace for RTAB-Map. Default: &#039;&#039;loc&#039;&#039;.&lt;br /&gt;
* &#039;&#039;map_location&#039;&#039; - selects the database path. &#039;&#039;lab&#039;&#039; maps to &#039;&#039;~/.ros/rtabmap.db&#039;&#039; and &#039;&#039;asta&#039;&#039; maps to &#039;&#039;~/.ros/rtabmap_asta.db&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
Sensor remappings:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;rgb/image -&amp;gt; /camera/color/image_raw&#039;&#039;&lt;br /&gt;
* &#039;&#039;rgb/camera_info -&amp;gt; /camera/color/camera_info&#039;&#039;&lt;br /&gt;
* &#039;&#039;depth/image -&amp;gt; /camera/depth/image_rect_raw&#039;&#039;&lt;br /&gt;
* &#039;&#039;depth/camera_info -&amp;gt; /camera/depth/camera_info&#039;&#039;&lt;br /&gt;
* &#039;&#039;imu -&amp;gt; /loc/imu&#039;&#039;&lt;br /&gt;
* &#039;&#039;scan -&amp;gt; /fejemis/scan&#039;&#039;&lt;br /&gt;
* &#039;&#039;initialpose -&amp;gt; /initialpose&#039;&#039;&lt;br /&gt;
* &#039;&#039;grid_prob_map -&amp;gt; /map&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Nodes:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;depthimage_to_laserscan_node&#039;&#039; converts the depth image into &#039;&#039;/camera_scan&#039;&#039;.&lt;br /&gt;
* &#039;&#039;rtabmap_sync/rgbd_sync&#039;&#039; synchronises RGB-D data on the real robot.&lt;br /&gt;
* &#039;&#039;rtabmap_odom/icp_odometry&#039;&#039; publishes ICP odometry to &#039;&#039;/loc/rtabmap_odom&#039;&#039; on the real robot.&lt;br /&gt;
* &#039;&#039;rtabmap_slam/rtabmap&#039;&#039; runs either SLAM mode or localisation mode.&lt;br /&gt;
&lt;br /&gt;
In SLAM mode, &#039;&#039;delete_db_on_start=True&#039;&#039; and RTAB-Map is launched with &#039;&#039;-d&#039;&#039;, so the selected database is overwritten. In localisation mode, &#039;&#039;Mem/IncrementalMemory=False&#039;&#039; and &#039;&#039;Mem/InitWMWithAllNodes=True&#039;&#039;, so the existing map is loaded for localisation.&lt;br /&gt;
&lt;br /&gt;
=== launch/map.launch.py ===&lt;br /&gt;
&lt;br /&gt;
Starts a component-based Nav2 map server in namespace &#039;&#039;loc&#039;&#039;, using &#039;&#039;config/nav2_servers.yaml&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
Launch arguments:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;use_sim_time&#039;&#039; - forwarded to the map server.&lt;br /&gt;
* &#039;&#039;map_make_container&#039;&#039;, &#039;&#039;map_container&#039;&#039;, and &#039;&#039;map_container_ns&#039;&#039; - declared as container-related options. The current launch creates a container named &#039;&#039;_map&#039;&#039; with prefix/namespace &#039;&#039;map&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
The map server is configured with:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;topic_name=/loc/map&#039;&#039;&lt;br /&gt;
* &#039;&#039;frame_id=map&#039;&#039;&lt;br /&gt;
* &#039;&#039;yaml_filename=&amp;quot;&amp;quot;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
In the current system, the map is primarily produced by RTAB-Map, while the Nav2 map server exposes map-server functionality for the navigation stack.&lt;br /&gt;
&lt;br /&gt;
=== launch/planning.launch.py ===&lt;br /&gt;
&lt;br /&gt;
Starts the Nav2 planning and navigation servers, the Fejemis velocity relay, the brush service, and optionally the OpenNav coverage server.&lt;br /&gt;
&lt;br /&gt;
Launch arguments:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;namespace&#039;&#039; - namespace for Nav2 nodes. Default: &#039;&#039;fejemis&#039;&#039;.&lt;br /&gt;
* &#039;&#039;use_sim_time&#039;&#039; - forwarded to nodes. Default: &#039;&#039;False&#039;&#039;.&lt;br /&gt;
* &#039;&#039;enable_coverage&#039;&#039; - enables &#039;&#039;opennav_coverage&#039;&#039;. The default is detected from whether the coverage packages are installed.&lt;br /&gt;
&lt;br /&gt;
Nodes:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;nav2_controller/controller_server&#039;&#039; named &#039;&#039;controller&#039;&#039;.&lt;br /&gt;
* &#039;&#039;fejemis_maploc/nav2_cmd_vel_deg_relay.py&#039;&#039;.&lt;br /&gt;
* &#039;&#039;nav2_behaviors/behavior_server&#039;&#039; named &#039;&#039;behavior&#039;&#039;.&lt;br /&gt;
* &#039;&#039;nav2_planner/planner_server&#039;&#039; named &#039;&#039;planner_server&#039;&#039;.&lt;br /&gt;
* &#039;&#039;nav2_bt_navigator/bt_navigator&#039;&#039; named &#039;&#039;bt_navigator&#039;&#039;.&lt;br /&gt;
* &#039;&#039;nav2_map_server/map_server&#039;&#039;.&lt;br /&gt;
* &#039;&#039;fejemis_maploc/brush_control_service.py&#039;&#039; in namespace &#039;&#039;control/brush&#039;&#039;, named &#039;&#039;controller&#039;&#039;.&lt;br /&gt;
* &#039;&#039;opennav_coverage/opennav_coverage&#039;&#039; named &#039;&#039;coverage_server&#039;&#039;, when enabled.&lt;br /&gt;
&lt;br /&gt;
Important remappings:&lt;br /&gt;
&lt;br /&gt;
* Nav2 odometry is remapped from &#039;&#039;/behavior/odom&#039;&#039; to &#039;&#039;/loc/odom&#039;&#039;.&lt;br /&gt;
* Nav2 map input is remapped from &#039;&#039;/behavior/map&#039;&#039; to &#039;&#039;/loc/map&#039;&#039;.&lt;br /&gt;
* Nav2 controller output &#039;&#039;cmd_vel&#039;&#039; is remapped to &#039;&#039;/control/nav2/cmd_vel&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
The velocity relay then converts angular velocity units and republishes to &#039;&#039;/control/joy/cmd_vel&#039;&#039; for the existing Fejemis control path.&lt;br /&gt;
&lt;br /&gt;
The Nav2 configuration in &#039;&#039;config/nav2_servers.yaml&#039;&#039; uses:&lt;br /&gt;
&lt;br /&gt;
* Regulated Pure Pursuit as the path follower.&lt;br /&gt;
* Navfn as the global planner.&lt;br /&gt;
* Local costmap sources from LiDAR, camera scan, net scan, and cable scan.&lt;br /&gt;
* Global costmap with static map, LiDAR obstacle layer, and inflation layer.&lt;br /&gt;
* The OpenNav coverage navigator when coverage support is enabled.&lt;br /&gt;
&lt;br /&gt;
=== launch/lifecycle.launch.py ===&lt;br /&gt;
&lt;br /&gt;
Starts Nav2 lifecycle managers.&lt;br /&gt;
&lt;br /&gt;
Launch arguments:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;in_sim&#039;&#039; - forwarded as &#039;&#039;use_sim_time&#039;&#039;.&lt;br /&gt;
* &#039;&#039;enable_loc_lfc&#039;&#039; - controls whether the localisation lifecycle manager is started. Default: &#039;&#039;false&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
Lifecycle managers:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;/fejemis/beh_lfc&#039;&#039; - behaviour/navigation lifecycle manager.&lt;br /&gt;
* &#039;&#039;/loc/loc_lfc&#039;&#039; - localisation lifecycle manager, only when &#039;&#039;enable_loc_lfc=true&#039;&#039;.&lt;br /&gt;
* &#039;&#039;/fejemis/hard_lfc&#039;&#039; - hardware lifecycle manager, disabled in simulation.&lt;br /&gt;
&lt;br /&gt;
Diagnostics are remapped to &#039;&#039;/diag/beh_lfc&#039;&#039;, &#039;&#039;/diag/loc_lfc&#039;&#039;, and &#039;&#039;/diag/hard_lfc&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
=== launch/lifecycle_managers_only.launch.py ===&lt;br /&gt;
&lt;br /&gt;
Starts the navigation-side launch files without the heavy sensor components. This is useful when another machine is responsible for LiDAR, RealSense, or RTAB-Map.&lt;br /&gt;
&lt;br /&gt;
Launch arguments:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;in_sim&#039;&#039; - enables simulation time.&lt;br /&gt;
* &#039;&#039;localization&#039;&#039; - when true, starts the planning stack on the main Pi. Default: &#039;&#039;false&#039;&#039;.&lt;br /&gt;
* &#039;&#039;enable_coverage&#039;&#039; - enables coverage navigation. Default: &#039;&#039;true&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
Included launch files:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;planning.launch.py&#039;&#039; is included only when &#039;&#039;in_sim=true&#039;&#039; or &#039;&#039;localization=true&#039;&#039;.&lt;br /&gt;
* &#039;&#039;lifecycle.launch.py&#039;&#039; is included immediately.&lt;br /&gt;
* &#039;&#039;behavior.launch.py&#039;&#039; is included after a 30 second delay, giving Nav2 servers time to become active before the BT engine connects to action servers.&lt;br /&gt;
&lt;br /&gt;
=== launch/behavior.launch.py ===&lt;br /&gt;
&lt;br /&gt;
Starts the Fejemis behaviour tree engine from &#039;&#039;raubase_core&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
Launch argument:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;use_sim_time&#039;&#039; - forwarded to the BT engine.&lt;br /&gt;
&lt;br /&gt;
The launch file selects the behaviour tree XML based on the ROS distribution:&lt;br /&gt;
&lt;br /&gt;
* Older BehaviourTree.CPP format: &#039;&#039;resources/behavior/main.xml&#039;&#039;.&lt;br /&gt;
* BehaviourTree.CPP v4 format: &#039;&#039;resources/behavior/main4.xml&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
The node launched is:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;raubase_core/bt_engine&#039;&#039;, configured by &#039;&#039;config/bt_engine.yaml&#039;&#039; in namespace &#039;&#039;fejemis&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
The BT plugin configuration loads:&lt;br /&gt;
&lt;br /&gt;
* raubase logging, mission, blackboard, coverage, and mixer plugins.&lt;br /&gt;
* Nav2 action plugins for single trigger, follow path, drive on heading, spin, and navigate to pose.&lt;br /&gt;
* OpenNav coverage path computation plugin.&lt;br /&gt;
* The package&#039;s custom &#039;&#039;fejemis_call_trigger_service_bt_node&#039;&#039; plugin.&lt;br /&gt;
&lt;br /&gt;
=== launch/robot.launch.py inclusion ===&lt;br /&gt;
&lt;br /&gt;
The top-level &#039;&#039;fejemis&#039;&#039; package includes &#039;&#039;fejemis_maploc&#039;&#039; from &#039;&#039;fejemis/launch/robot.launch.py&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
Typical distribution:&lt;br /&gt;
&lt;br /&gt;
* Main Pi: bridge control, odometry, lifecycle managers, behaviour tree, and planning when localisation is enabled.&lt;br /&gt;
* Auxiliary Pi: LiDAR, RTAB-Map, RealSense, and optional vision.&lt;br /&gt;
&lt;br /&gt;
The top-level launch waits 3 seconds after bridge startup before launching the rest of the stack.&lt;br /&gt;
&lt;br /&gt;
== Script Nodes ==&lt;br /&gt;
&lt;br /&gt;
=== nav2_cmd_vel_deg_relay.py ===&lt;br /&gt;
&lt;br /&gt;
Purpose: adapts Nav2 velocity commands to the Fejemis control convention.&lt;br /&gt;
&lt;br /&gt;
Subscriptions:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;/control/nav2/cmd_vel&#039;&#039; - &#039;&#039;geometry_msgs/Twist&#039;&#039;, with angular velocity in rad/s.&lt;br /&gt;
&lt;br /&gt;
Publications:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;/control/joy/cmd_vel&#039;&#039; - &#039;&#039;geometry_msgs/Twist&#039;&#039;, with angular velocity converted to deg/s.&lt;br /&gt;
&lt;br /&gt;
The linear fields are copied unchanged. The angular x, y, and z fields are multiplied by 180/pi. This is needed because Nav2 uses the normal ROS convention of rad/s, while the existing Fejemis bridge control path expects angular velocity in deg/s.&lt;br /&gt;
&lt;br /&gt;
=== brush_control_service.py ===&lt;br /&gt;
&lt;br /&gt;
Purpose: exposes simple ROS services for turning the brush on and off.&lt;br /&gt;
&lt;br /&gt;
Parameters:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;manage_topic&#039;&#039; - topic for &#039;&#039;fejemis_bridge/msg/SteerManage&#039;&#039;. Default: &#039;&#039;/fejemis/front/manage&#039;&#039;.&lt;br /&gt;
* &#039;&#039;qos_depth&#039;&#039; - publisher QoS depth. Default: &#039;&#039;10&#039;&#039;.&lt;br /&gt;
* &#039;&#039;initial_brush_on&#039;&#039; - initial internal state. Default: &#039;&#039;False&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
Publications:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;manage_topic&#039;&#039; - publishes &#039;&#039;SteerManage.BRUSH_ON&#039;&#039; or &#039;&#039;SteerManage.BRUSH_OFF&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
Services:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;~/set&#039;&#039; - &#039;&#039;std_srvs/SetBool&#039;&#039;, true means brush on and false means brush off.&lt;br /&gt;
* &#039;&#039;~/on&#039;&#039; - &#039;&#039;std_srvs/Trigger&#039;&#039;, turns the brush on.&lt;br /&gt;
* &#039;&#039;~/off&#039;&#039; - &#039;&#039;std_srvs/Trigger&#039;&#039;, turns the brush off.&lt;br /&gt;
&lt;br /&gt;
In &#039;&#039;planning.launch.py&#039;&#039; this node runs as &#039;&#039;/control/brush/controller&#039;&#039;, which creates the services used by the behaviour tree:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;/control/brush/controller/on&#039;&#039;&lt;br /&gt;
* &#039;&#039;/control/brush/controller/off&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
=== rviz_goal_relay.py ===&lt;br /&gt;
&lt;br /&gt;
Purpose: lets an operator send a Nav2 goal from RViz.&lt;br /&gt;
&lt;br /&gt;
Subscriptions:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;/goal_pose&#039;&#039; - RViz 2D Nav Goal as &#039;&#039;geometry_msgs/PoseStamped&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
Action clients:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;/fejemis/navigate_to_pose&#039;&#039; - &#039;&#039;nav2_msgs/action/NavigateToPose&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
When a goal is clicked in RViz, the node checks that the action server is ready, forwards the goal, and logs whether it was accepted and whether it succeeded.&lt;br /&gt;
&lt;br /&gt;
=== mission_trigger.py ===&lt;br /&gt;
&lt;br /&gt;
Purpose: starts a cleaning mission by publishing blackboard values and then setting the active mission.&lt;br /&gt;
&lt;br /&gt;
Parameters:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;mission&#039;&#039; - mission string to publish. Default: &#039;&#039;cleaning/waypoint&#039;&#039;.&lt;br /&gt;
* &#039;&#039;dock_pose&#039;&#039; - dock pose as &#039;&#039;[x, y, yaw]&#039;&#039;. Default: &#039;&#039;[2.62, 3.9, 0.0]&#039;&#039;.&lt;br /&gt;
* &#039;&#039;waypoint_pose&#039;&#039; - waypoint pose as &#039;&#039;[x, y, yaw]&#039;&#039;. Default: &#039;&#039;[2.10491, 0.497438, 3.11127]&#039;&#039;.&lt;br /&gt;
* &#039;&#039;clean_poly&#039;&#039; - polygon string used for cleaning area blackboard values.&lt;br /&gt;
* &#039;&#039;startup_delay&#039;&#039; - delay before publishing. Default: &#039;&#039;2.0&#039;&#039; seconds.&lt;br /&gt;
&lt;br /&gt;
Publications:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;/blackboard/dock_x&#039;&#039;, &#039;&#039;/blackboard/dock_y&#039;&#039;, &#039;&#039;/blackboard/dock_yaw&#039;&#039;&lt;br /&gt;
* &#039;&#039;/blackboard/waypoint_x&#039;&#039;, &#039;&#039;/blackboard/waypoint_y&#039;&#039;, &#039;&#039;/blackboard/waypoint_yaw&#039;&#039;&lt;br /&gt;
* &#039;&#039;/blackboard/field_polygon&#039;&#039;&lt;br /&gt;
* &#039;&#039;/blackboard/clean_poly&#039;&#039;&lt;br /&gt;
* &#039;&#039;/blackboard/start_pose&#039;&#039;&lt;br /&gt;
* &#039;&#039;/blackboard/dock_pose&#039;&#039;&lt;br /&gt;
* &#039;&#039;/blackboard/waypoint_pose&#039;&#039;&lt;br /&gt;
* &#039;&#039;/behavior/set_mission&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The blackboard publishers use transient-local QoS so late subscribers can still receive the latest values. Pose strings are encoded as:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
stamp;frame;x;y;z;qx;qy;qz;qw&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The node also subscribes to &#039;&#039;/behavior/set_mission&#039;&#039;. When it sees &#039;&#039;auto/idle&#039;&#039; after the mission has run, it logs completion and shuts down.&lt;br /&gt;
&lt;br /&gt;
=== complete_coverage_planner.py ===&lt;br /&gt;
&lt;br /&gt;
Purpose: interactive coverage test node for RViz.&lt;br /&gt;
&lt;br /&gt;
Parameters:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;rviz_click_topic&#039;&#039; - clicked point topic. Default: &#039;&#039;/clicked_point&#039;&#039;.&lt;br /&gt;
* &#039;&#039;coverage_frame&#039;&#039; - frame for the coverage polygon. Default: &#039;&#039;map&#039;&#039;.&lt;br /&gt;
* &#039;&#039;min_polygon_points&#039;&#039; - number of clicked points before sending a goal. Default: &#039;&#039;4&#039;&#039;.&lt;br /&gt;
* &#039;&#039;auto_close_polygon&#039;&#039; - whether to append the first point as the final point. Default: &#039;&#039;True&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
Subscriptions:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;/clicked_point&#039;&#039; by default - RViz point clicks.&lt;br /&gt;
&lt;br /&gt;
Action clients:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;fejemis/navigate_complete_coverage&#039;&#039; - &#039;&#039;opennav_coverage_msgs/action/NavigateCompleteCoverage&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
The node waits for &#039;&#039;fejemis/bt_navigator&#039;&#039; to become active, collects clicked points in RViz, converts them to a &#039;&#039;geometry_msgs/Polygon&#039;&#039;, and sends a coverage navigation action once enough points have been collected.&lt;br /&gt;
&lt;br /&gt;
=== fixed_complete_coverage_planner.py ===&lt;br /&gt;
&lt;br /&gt;
Purpose: fixed-polygon coverage test node.&lt;br /&gt;
&lt;br /&gt;
Parameters:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;coverage_frame&#039;&#039; - frame for the coverage polygon. Default: &#039;&#039;map&#039;&#039;.&lt;br /&gt;
* &#039;&#039;min_polygon_points&#039;&#039; - retained from the interactive tester. Default: &#039;&#039;4&#039;&#039;.&lt;br /&gt;
* &#039;&#039;auto_close_polygon&#039;&#039; - whether to append the first point as the final point. Default: &#039;&#039;True&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
Action clients:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;fejemis/navigate_complete_coverage&#039;&#039; - &#039;&#039;opennav_coverage_msgs/action/NavigateCompleteCoverage&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
The node waits for &#039;&#039;fejemis/bt_navigator&#039;&#039; to become active, then sends a fixed four-point polygon:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
(-5.35,  2.45)&lt;br /&gt;
( 2.83,  3.18)&lt;br /&gt;
( 3.22, -5.08)&lt;br /&gt;
(-4.76, -5.97)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This is useful for testing coverage planning without clicking points manually in RViz.&lt;br /&gt;
&lt;br /&gt;
== C++ Behaviour Tree Plugin ==&lt;br /&gt;
&lt;br /&gt;
=== fejemis_call_trigger_service_bt_node ===&lt;br /&gt;
&lt;br /&gt;
The C++ plugin &#039;&#039;src/bt_plugins/call_trigger_service_bt_node.cpp&#039;&#039; registers the behaviour tree node &#039;&#039;CallTriggerService&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
Inputs:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;service_name&#039;&#039; - service to call.&lt;br /&gt;
* &#039;&#039;wait_for_service_ms&#039;&#039; - timeout while waiting for service availability. Default: &#039;&#039;1000&#039;&#039;.&lt;br /&gt;
* &#039;&#039;call_timeout_ms&#039;&#039; - timeout for the service call. Default: &#039;&#039;2000&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
The node creates a &#039;&#039;std_srvs/Trigger&#039;&#039; client, waits for the target service, calls it, and returns:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;SUCCESS&#039;&#039; when the service exists, responds before timeout, and returns &#039;&#039;success=true&#039;&#039;.&lt;br /&gt;
* &#039;&#039;FAILURE&#039;&#039; when the service is unavailable, the call times out, or the response reports failure.&lt;br /&gt;
&lt;br /&gt;
The cleaning subtree uses it to call:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;/control/brush/controller/on&#039;&#039;&lt;br /&gt;
* &#039;&#039;/control/brush/controller/off&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
== Behaviour Tree ==&lt;br /&gt;
&lt;br /&gt;
The active behaviour tree on newer ROS distributions is &#039;&#039;resources/behavior/main4.xml&#039;&#039;. It is loaded by &#039;&#039;behavior.launch.py&#039;&#039; through the &#039;&#039;raubase_core/bt_engine&#039;&#039; node.&lt;br /&gt;
&lt;br /&gt;
=== Main Loop ===&lt;br /&gt;
&lt;br /&gt;
The root tree is &#039;&#039;main&#039;&#039;:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
main&lt;br /&gt;
`-- Sequence&lt;br /&gt;
    |-- SingleTrigger&lt;br /&gt;
    |   `-- t_init&lt;br /&gt;
    `-- KeepRunningUntilFailure&lt;br /&gt;
        `-- ReactiveSequence&lt;br /&gt;
            |-- ForceSuccess&lt;br /&gt;
            |   `-- t_passive&lt;br /&gt;
            `-- t_runtime&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The structure means:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;t_init&#039;&#039; runs once at startup.&lt;br /&gt;
* &#039;&#039;t_passive&#039;&#039; runs repeatedly and updates blackboard/mission state.&lt;br /&gt;
* &#039;&#039;t_runtime&#039;&#039; selects the active behaviour based on the current mission.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Visualization placeholder:&#039;&#039;&#039; exported Groot image of &#039;&#039;main4.xml&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
=== t_init ===&lt;br /&gt;
&lt;br /&gt;
Initialises the robot behaviour state:&lt;br /&gt;
&lt;br /&gt;
* Sets mission to &#039;&#039;auto/idle&#039;&#039;.&lt;br /&gt;
* Sets blackboard key &#039;&#039;initialized=1&#039;&#039;.&lt;br /&gt;
* Sets calibration square distance &#039;&#039;calib_sq_dist=7&#039;&#039;.&lt;br /&gt;
* Opens the mixer port using &#039;&#039;OpenMixerPort&#039;&#039; with priority 4 and topic &#039;&#039;/behavior&#039;&#039;.&lt;br /&gt;
* Logs that the robot is initialized.&lt;br /&gt;
&lt;br /&gt;
=== t_passive ===&lt;br /&gt;
&lt;br /&gt;
Runs as the passive safety/update loop:&lt;br /&gt;
&lt;br /&gt;
* Logs &#039;&#039;Safety loop&#039;&#039; at debug level.&lt;br /&gt;
* Refreshes &#039;&#039;start_pose&#039;&#039;, &#039;&#039;waypoint_pose&#039;&#039;, &#039;&#039;clean_poly&#039;&#039;, and &#039;&#039;field_polygon&#039;&#039; from &#039;&#039;/blackboard/...&#039;&#039; topics.&lt;br /&gt;
* Refreshes the current mission from &#039;&#039;/behavior/set_mission&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
=== t_runtime ===&lt;br /&gt;
&lt;br /&gt;
Selects behaviour using a reactive fallback:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
t_runtime&lt;br /&gt;
|-- t_idle_runtime&lt;br /&gt;
|-- t_calib&lt;br /&gt;
|-- t_mapping&lt;br /&gt;
|-- t_cleaning&lt;br /&gt;
`-- unknown mission handler -&amp;gt; set idle&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The first matching mission branch runs. If no known branch matches, the tree logs an error and returns to idle.&lt;br /&gt;
&lt;br /&gt;
=== Idle ===&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;t_idle_runtime&#039;&#039; succeeds when the current mission is:&lt;br /&gt;
&lt;br /&gt;
* mission: &#039;&#039;auto&#039;&#039;&lt;br /&gt;
* mission_sub: &#039;&#039;idle&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
This keeps the robot in a waiting state. Sensors, telemetry, and subscriptions remain active, but no movement commands are issued. The robot leaves this state when a new mission is received through &#039;&#039;/behavior/set_mission&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
=== Calibration ===&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;t_calib&#039;&#039; chooses between line calibration and square calibration.&lt;br /&gt;
&lt;br /&gt;
Line calibration, &#039;&#039;t_calib_line&#039;&#039;:&lt;br /&gt;
&lt;br /&gt;
* Requires mission &#039;&#039;calib/line&#039;&#039;.&lt;br /&gt;
* Drives forward using &#039;&#039;DriveOnHeading&#039;&#039; &lt;br /&gt;
* &#039;&#039;calib_sq_dist&#039;&#039; is set to 0.05 m/s during initialization.&lt;br /&gt;
* Returns to idle.&lt;br /&gt;
&lt;br /&gt;
Square calibration, &#039;&#039;t_calib_square&#039;&#039;:&lt;br /&gt;
&lt;br /&gt;
* Requires mission &#039;&#039;calib/*&#039;&#039;.&lt;br /&gt;
* Runs &#039;&#039;f_calib_side&#039;&#039; four times.&lt;br /&gt;
* Each side drives forward and spins 1.57 rad.&lt;br /&gt;
* Returns to idle.&lt;br /&gt;
Each &#039;&#039;f_calib_side&#039;&#039; contains:&lt;br /&gt;
&lt;br /&gt;
*DriveOnHeading&lt;br /&gt;
*Spin&lt;br /&gt;
*A quarter-turn style calibration step&lt;br /&gt;
Calibration succeeds when each action in the sequence completes successfully.&lt;br /&gt;
&lt;br /&gt;
=== Mapping ===&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;t_mapping&#039;&#039; currently checks for mission &#039;&#039;mapping/*&#039;&#039; and then returns to idle. This branch is a placeholder for future autonomous mapping behaviour.&lt;br /&gt;
&lt;br /&gt;
=== Cleaning ===&lt;br /&gt;
&lt;br /&gt;
The current cleaning branch is &#039;&#039;t_cleaning&#039;&#039;:&lt;br /&gt;
&lt;br /&gt;
* Requires mission &#039;&#039;cleaning/waypoint&#039;&#039; with mission_sub &#039;&#039;idle&#039;&#039;.&lt;br /&gt;
* Navigates to &#039;&#039;{waypoint_pose}&#039;&#039; using &#039;&#039;NavigateToPose&#039;&#039;.&lt;br /&gt;
* Sets current mission to &#039;&#039;cleaning/area&#039;&#039;.&lt;br /&gt;
* Returns to idle.&lt;br /&gt;
&lt;br /&gt;
Supporting cleaning subtrees are already present:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;f_get_area_to_clean&#039;&#039; sets a polygon and calls &#039;&#039;ComputeCoveragePath&#039;&#039;.&lt;br /&gt;
* &#039;&#039;f_clean_area&#039;&#039; turns the brush on, follows &#039;&#039;{cover_nav_path}&#039;&#039;, and turns the brush off.&lt;br /&gt;
* &#039;&#039;f_go_to_path_start&#039;&#039; is a placeholder that logs navigation to the path start.&lt;br /&gt;
&lt;br /&gt;
These subtrees show the intended full cleaning sequence, although the active &#039;&#039;t_cleaning&#039;&#039; branch currently only navigates to a waypoint and changes mission state.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Diagram placeholder:&#039;&#039;&#039; cleaning behaviour sequence: mission trigger -&amp;gt; waypoint navigation -&amp;gt; coverage path computation -&amp;gt; brush on -&amp;gt; follow coverage path -&amp;gt; brush off -&amp;gt; idle.&lt;br /&gt;
&lt;br /&gt;
== Configuration Files ==&lt;br /&gt;
&lt;br /&gt;
=== config/odometry.yaml and config/odometry_sim.yaml ===&lt;br /&gt;
&lt;br /&gt;
Configure the EKF and IMU filter. The real configuration fuses wheel odometry and RTAB-Map ICP odometry in 2D mode. The simulation configuration is separated to avoid conflicting simulated IMU orientation.&lt;br /&gt;
&lt;br /&gt;
=== config/rtabmap.yaml and config/rtabmap_sim.yaml ===&lt;br /&gt;
&lt;br /&gt;
Configure RTAB-Map for real robot and simulation use. &#039;&#039;rtabmap_slam.launch.py&#039;&#039; selects between them using &#039;&#039;use_sim_time&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
=== config/nav2_servers.yaml and config/nav2_sim.yaml ===&lt;br /&gt;
&lt;br /&gt;
Configure Nav2 controller, planner, behaviour server, costmaps, BT navigator, coverage server, and map server. The main real-robot launch uses &#039;&#039;nav2_servers.yaml&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
=== config/bt_engine.yaml ===&lt;br /&gt;
&lt;br /&gt;
Lists behaviour tree plugins loaded by &#039;&#039;raubase_core/bt_engine&#039;&#039;, including the Fejemis custom trigger-service plugin.&lt;br /&gt;
&lt;br /&gt;
=== config/lifecycle.yaml ===&lt;br /&gt;
&lt;br /&gt;
Configures lifecycle manager node lists and autostart behaviour for the navigation, localisation, and hardware lifecycle managers.&lt;br /&gt;
&lt;br /&gt;
=== config/realsense.yaml ===&lt;br /&gt;
&lt;br /&gt;
RealSense camera configuration loaded by &#039;&#039;realsense.launch.py&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
== Typical Usage ==&lt;br /&gt;
&lt;br /&gt;
Start the complete robot stack through the top-level launch:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ros2 launch fejemis robot.launch.py localization:=true map_location:=asta&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Run on both Raspberry Pis. The launch file decides which parts run on the main and auxiliary computers through the &#039;&#039;is_aux&#039;&#039; argument.&lt;br /&gt;
&lt;br /&gt;
Start a waypoint cleaning mission after the system is up:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ros2 run fejemis_maploc mission_trigger.py&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Send an RViz navigation goal:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ros2 run fejemis_maploc rviz_goal_relay.py&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Test coverage navigation with RViz clicked points:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ros2 run fejemis_maploc complete_coverage_planner.py&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Test coverage navigation with the fixed polygon:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ros2 run fejemis_maploc fixed_complete_coverage_planner.py&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Notes and Future Work ==&lt;br /&gt;
&lt;br /&gt;
* Clarify which Raspberry Pi should run each launch file in the final deployment.&lt;br /&gt;
* Expand the cleaning behaviour section once &#039;&#039;t_cleaning&#039;&#039; is connected to &#039;&#039;f_get_area_to_clean&#039;&#039; and &#039;&#039;f_clean_area&#039;&#039; in the active tree.&lt;/div&gt;</summary>
		<author><name>S252724</name></author>
	</entry>
	<entry>
		<id>https://rsewiki.electro.dtu.dk/index.php?title=Fejemis_Maploc_Ros2&amp;diff=8966</id>
		<title>Fejemis Maploc Ros2</title>
		<link rel="alternate" type="text/html" href="https://rsewiki.electro.dtu.dk/index.php?title=Fejemis_Maploc_Ros2&amp;diff=8966"/>
		<updated>2026-05-28T10:09:23Z</updated>

		<summary type="html">&lt;p&gt;S252724: /* Idle */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Back to [[Fejemis ROS2 Software]]&lt;br /&gt;
&lt;br /&gt;
== Purpose ==&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;fejemis_maploc&#039;&#039; is the mapping, localisation, navigation, and behaviour package for Fejemis. It connects the robot&#039;s sensor data to the navigation stack and exposes the high-level behaviour tree used to run calibration, mapping, waypoint navigation, and cleaning missions.&lt;br /&gt;
&lt;br /&gt;
The package sits between the low-level bridge and the high-level mission logic:&lt;br /&gt;
&lt;br /&gt;
* The bridge publishes wheel odometry, IMU data, and accepts velocity / brush commands.&lt;br /&gt;
* &#039;&#039;fejemis_maploc&#039;&#039; filters odometry, starts LiDAR and RealSense sensing, runs RTAB-Map, starts Nav2, and runs the Fejemis behaviour tree.&lt;br /&gt;
* The behaviour tree sends commands to Nav2, the coverage planner, the mixer, and the brush service.&lt;br /&gt;
[[File:maploc_general_block_diagram.png| 1600px]]&lt;br /&gt;
&lt;br /&gt;
Overall &#039;&#039;fejemis_maploc&#039;&#039; data flow from bridge, LiDAR, and RealSense into odometry, RTAB-Map, Nav2, coverage planning, behaviour tree, and robot commands.&lt;br /&gt;
&lt;br /&gt;
== Package Layout ==&lt;br /&gt;
&lt;br /&gt;
Important package folders:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;resources/launch&#039;&#039; - ROS 2 launch files for sensors, odometry, SLAM/localisation, planning, lifecycle managers, and behaviour trees.&lt;br /&gt;
* &#039;&#039;resources/config&#039;&#039; - YAML configuration for odometry, Nav2, RTAB-Map, RealSense, lifecycle managers, and BT plugins.&lt;br /&gt;
* &#039;&#039;resources/behavior&#039;&#039; - behaviour tree XML files. &#039;&#039;main.xml&#039;&#039; is the older BT.CPP format and &#039;&#039;main4.xml&#039;&#039; is the BT.CPP v4 version used on newer ROS distributions.&lt;br /&gt;
* &#039;&#039;scripts&#039;&#039; - Python helper nodes for mission triggering, RViz goal relays, brush services, velocity conversion, and coverage tests.&lt;br /&gt;
* &#039;&#039;src/bt_plugins&#039;&#039; - C++ behaviour tree plugin used by the cleaning behaviour.&lt;br /&gt;
&lt;br /&gt;
== Main Topics and Frames ==&lt;br /&gt;
&lt;br /&gt;
Common frame names:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;map&#039;&#039; - global map frame produced by RTAB-Map / Nav2.&lt;br /&gt;
* &#039;&#039;odom&#039;&#039; - local odometry frame.&lt;br /&gt;
* &#039;&#039;base_link&#039;&#039; - robot base frame.&lt;br /&gt;
* &#039;&#039;lidar_link&#039;&#039; - LiDAR frame.&lt;br /&gt;
* &#039;&#039;camera_camera_link&#039;&#039; and RealSense optical frames - camera frames used by the RealSense driver and RTAB-Map.&lt;br /&gt;
&lt;br /&gt;
Important topics and actions:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;/fejemis/odom&#039;&#039; - wheel odometry from the bridge.&lt;br /&gt;
* &#039;&#039;/fejemis/imu&#039;&#039; and &#039;&#039;/fejemis/mag&#039;&#039; - raw IMU and magnetometer inputs.&lt;br /&gt;
* &#039;&#039;/loc/imu&#039;&#039; - filtered IMU output from Madgwick.&lt;br /&gt;
* &#039;&#039;/loc/odom&#039;&#039; - fused odometry output from the EKF.&lt;br /&gt;
* &#039;&#039;/fejemis/scan&#039;&#039; - LiDAR scan.&lt;br /&gt;
* &#039;&#039;/camera/color/image_raw&#039;&#039;, &#039;&#039;/camera/depth/image_rect_raw&#039;&#039;, and camera info topics - RealSense RGB-D input.&lt;br /&gt;
* &#039;&#039;/camera_scan&#039;&#039; - depth-image-to-laserscan output used as an extra obstacle source.&lt;br /&gt;
* &#039;&#039;/loc/map&#039;&#039; - occupancy map used by Nav2.&lt;br /&gt;
* &#039;&#039;/control/nav2/cmd_vel&#039;&#039; - Nav2 velocity command before Fejemis-specific conversion.&lt;br /&gt;
* &#039;&#039;/control/joy/cmd_vel&#039;&#039; - velocity command sent into the existing Fejemis control path.&lt;br /&gt;
* &#039;&#039;/behavior/set_mission&#039;&#039; - mission command topic consumed by the behaviour tree.&lt;br /&gt;
* &#039;&#039;/blackboard/...&#039;&#039; - blackboard values mirrored into the behaviour tree.&lt;br /&gt;
* &#039;&#039;/fejemis/navigate_to_pose&#039;&#039; - Nav2 action used for waypoint goals.&lt;br /&gt;
* &#039;&#039;/fejemis/navigate_complete_coverage&#039;&#039; - OpenNav coverage action used for coverage missions.&lt;br /&gt;
* &#039;&#039;/control/brush/controller/on&#039;&#039; and &#039;&#039;/control/brush/controller/off&#039;&#039; - brush control trigger services.&lt;br /&gt;
&lt;br /&gt;
== Launch Files ==&lt;br /&gt;
&lt;br /&gt;
The package is designed so the top-level &#039;&#039;fejemis/launch/robot.launch.py&#039;&#039; can include only the parts needed on each Raspberry Pi. The main Pi typically handles bridge control and odometry, while the auxiliary Pi handles heavier sensing such as LiDAR, RealSense, RTAB-Map, and vision.&lt;br /&gt;
&lt;br /&gt;
=== launch/lidar.launch.py ===&lt;br /&gt;
&lt;br /&gt;
Starts the LD19 LiDAR driver from &#039;&#039;ldlidar_stl_ros2&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
Launch arguments:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;ld_make_container&#039;&#039; - whether the LiDAR launch should create a component container. Default: &#039;&#039;True&#039;&#039;.&lt;br /&gt;
* &#039;&#039;ld_container&#039;&#039; - component container name. Default: &#039;&#039;_laser&#039;&#039;.&lt;br /&gt;
* &#039;&#039;ld_container_ns&#039;&#039; - namespace for the component container. Default: &#039;&#039;loc&#039;&#039;.&lt;br /&gt;
* &#039;&#039;port_name&#039;&#039; - serial port for the LiDAR. Default: &#039;&#039;/dev/ttyUSB0&#039;&#039;.&lt;br /&gt;
* &#039;&#039;namespace&#039;&#039; - launch argument declared for compatibility. The included launch currently receives &#039;&#039;fejemis&#039;&#039; as namespace.&lt;br /&gt;
&lt;br /&gt;
Important configuration passed to the driver:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;laser_frame=lidar_link&#039;&#039;&lt;br /&gt;
* &#039;&#039;clockwise_dir=false&#039;&#039;&lt;br /&gt;
* &#039;&#039;range_min=0.2&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The resulting scan is used by RTAB-Map and by the Nav2 costmaps as the main 2D obstacle source.&lt;br /&gt;
&lt;br /&gt;
=== launch/tf.launch.py ===&lt;br /&gt;
&lt;br /&gt;
Publishes static transforms for the physical sensor layout.&lt;br /&gt;
&lt;br /&gt;
Published transforms:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;base_link -&amp;gt; camera_camera_link&#039;&#039; at approximately x=-0.24 m, y=0.0 m, z=0.44 m.&lt;br /&gt;
* On the real robot, &#039;&#039;base_link -&amp;gt; lidar_link&#039;&#039; at approximately x=-0.29 m, y=0.0 m, z=0.25 m, yaw=-1.5708 rad. The yaw correction compensates for the LiDAR&#039;s physical mounting angle.&lt;br /&gt;
* In simulation, &#039;&#039;laser_frame -&amp;gt; lidar_link&#039;&#039; is published instead, because Gazebo already provides &#039;&#039;laser_frame&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
The launch file uses the &#039;&#039;in_sim&#039;&#039; launch configuration to select the real or simulated LiDAR transform.&lt;br /&gt;
&lt;br /&gt;
=== launch/odometry.launch.py ===&lt;br /&gt;
&lt;br /&gt;
Starts the odometry estimation path. On the real robot it runs both the Madgwick IMU filter and the robot_localization EKF. In simulation it only runs the EKF with the simulation-specific configuration.&lt;br /&gt;
&lt;br /&gt;
Launch argument:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;use_sim_time&#039;&#039; - selects real or simulated odometry configuration. Default: &#039;&#039;False&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
Real robot nodes:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;imu_filter_madgwick_node&#039;&#039; named &#039;&#039;imu_filter&#039;&#039; in namespace &#039;&#039;loc&#039;&#039;.&lt;br /&gt;
* &#039;&#039;robot_localization/ekf_node&#039;&#039; named &#039;&#039;ekf&#039;&#039; in namespace &#039;&#039;loc&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
Real robot remappings:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;imu/mag -&amp;gt; /fejemis/mag&#039;&#039;&lt;br /&gt;
* &#039;&#039;imu/data_raw -&amp;gt; /fejemis/imu&#039;&#039;&lt;br /&gt;
* &#039;&#039;imu/data -&amp;gt; imu&#039;&#039;, which resolves to &#039;&#039;/loc/imu&#039;&#039;.&lt;br /&gt;
* &#039;&#039;/loc/odom_in -&amp;gt; /fejemis/odom&#039;&#039;&lt;br /&gt;
* &#039;&#039;odometry/filtered -&amp;gt; odom&#039;&#039;, which resolves to &#039;&#039;/loc/odom&#039;&#039;.&lt;br /&gt;
* diagnostics are remapped to &#039;&#039;/loc/ekf/_diagnostics&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
The EKF configuration in &#039;&#039;config/odometry.yaml&#039;&#039; runs at 30 Hz, uses two-dimensional mode, publishes TF, and fuses:&lt;br /&gt;
&lt;br /&gt;
* Wheel odometry from &#039;&#039;/fejemis/odom&#039;&#039;.&lt;br /&gt;
* RTAB-Map ICP odometry from &#039;&#039;/loc/rtabmap_odom&#039;&#039;.&lt;br /&gt;
* Filtered IMU input is present as &#039;&#039;/loc/imu&#039;&#039;, although the current real configuration disables the IMU state variables in the EKF and relies more heavily on odometry sources for motion estimation.&lt;br /&gt;
&lt;br /&gt;
Simulation mode uses &#039;&#039;config/odometry_sim.yaml&#039;&#039; and avoids the Madgwick IMU orientation because the simulated IMU orientation can conflict with Gazebo odometry frame conventions.&lt;br /&gt;
&lt;br /&gt;
[[File:fejemis_maploc_ekf_block_diagram.png| 800px]]&lt;br /&gt;
&lt;br /&gt;
EKF input/output diagram: &#039;&#039;/fejemis/odom&#039;&#039;, &#039;&#039;/loc/rtabmap_odom&#039;&#039;, &#039;&#039;/loc/imu&#039;&#039; into &#039;&#039;/loc/ekf&#039;&#039;, output &#039;&#039;/loc/odom&#039;&#039; and TF.&lt;br /&gt;
&lt;br /&gt;
=== launch/realsense.launch.py ===&lt;br /&gt;
&lt;br /&gt;
Includes the official &#039;&#039;realsense2_camera&#039;&#039; launch file with the package&#039;s &#039;&#039;config/realsense.yaml&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
Launch arguments:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;camera_namespace&#039;&#039; - namespace for the camera. Default: empty string.&lt;br /&gt;
* &#039;&#039;use_sim_time&#039;&#039; - declared for consistency, although the included launch is mainly configured through the RealSense launch arguments.&lt;br /&gt;
&lt;br /&gt;
Important RealSense settings:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;publish_tf=true&#039;&#039;&lt;br /&gt;
* &#039;&#039;enable_sync=true&#039;&#039;&lt;br /&gt;
* &#039;&#039;unite_imu_method=2&#039;&#039;&lt;br /&gt;
* &#039;&#039;config_file=fejemis_maploc/config/realsense.yaml&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The camera publishes the RGB, depth, camera info, IMU, and TF data used by RTAB-Map and by the depth-to-scan obstacle conversion.&lt;br /&gt;
&lt;br /&gt;
=== launch/rtabmap_slam.launch.py ===&lt;br /&gt;
&lt;br /&gt;
Starts the RTAB-Map mapping/localisation pipeline and includes &#039;&#039;tf.launch.py&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
Launch arguments:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;use_sim_time&#039;&#039; - selects real or simulation config. Default: &#039;&#039;false&#039;&#039;.&lt;br /&gt;
* &#039;&#039;localization&#039;&#039; - if &#039;&#039;true&#039;&#039;, RTAB-Map loads an existing database and does not increment the map. If &#039;&#039;false&#039;&#039;, it starts in mapping mode and deletes the database on start. Default: &#039;&#039;false&#039;&#039;.&lt;br /&gt;
* &#039;&#039;namespace&#039;&#039; - namespace for RTAB-Map. Default: &#039;&#039;loc&#039;&#039;.&lt;br /&gt;
* &#039;&#039;map_location&#039;&#039; - selects the database path. &#039;&#039;lab&#039;&#039; maps to &#039;&#039;~/.ros/rtabmap.db&#039;&#039; and &#039;&#039;asta&#039;&#039; maps to &#039;&#039;~/.ros/rtabmap_asta.db&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
Sensor remappings:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;rgb/image -&amp;gt; /camera/color/image_raw&#039;&#039;&lt;br /&gt;
* &#039;&#039;rgb/camera_info -&amp;gt; /camera/color/camera_info&#039;&#039;&lt;br /&gt;
* &#039;&#039;depth/image -&amp;gt; /camera/depth/image_rect_raw&#039;&#039;&lt;br /&gt;
* &#039;&#039;depth/camera_info -&amp;gt; /camera/depth/camera_info&#039;&#039;&lt;br /&gt;
* &#039;&#039;imu -&amp;gt; /loc/imu&#039;&#039;&lt;br /&gt;
* &#039;&#039;scan -&amp;gt; /fejemis/scan&#039;&#039;&lt;br /&gt;
* &#039;&#039;initialpose -&amp;gt; /initialpose&#039;&#039;&lt;br /&gt;
* &#039;&#039;grid_prob_map -&amp;gt; /map&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Nodes:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;depthimage_to_laserscan_node&#039;&#039; converts the depth image into &#039;&#039;/camera_scan&#039;&#039;.&lt;br /&gt;
* &#039;&#039;rtabmap_sync/rgbd_sync&#039;&#039; synchronises RGB-D data on the real robot.&lt;br /&gt;
* &#039;&#039;rtabmap_odom/icp_odometry&#039;&#039; publishes ICP odometry to &#039;&#039;/loc/rtabmap_odom&#039;&#039; on the real robot.&lt;br /&gt;
* &#039;&#039;rtabmap_slam/rtabmap&#039;&#039; runs either SLAM mode or localisation mode.&lt;br /&gt;
&lt;br /&gt;
In SLAM mode, &#039;&#039;delete_db_on_start=True&#039;&#039; and RTAB-Map is launched with &#039;&#039;-d&#039;&#039;, so the selected database is overwritten. In localisation mode, &#039;&#039;Mem/IncrementalMemory=False&#039;&#039; and &#039;&#039;Mem/InitWMWithAllNodes=True&#039;&#039;, so the existing map is loaded for localisation.&lt;br /&gt;
&lt;br /&gt;
=== launch/map.launch.py ===&lt;br /&gt;
&lt;br /&gt;
Starts a component-based Nav2 map server in namespace &#039;&#039;loc&#039;&#039;, using &#039;&#039;config/nav2_servers.yaml&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
Launch arguments:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;use_sim_time&#039;&#039; - forwarded to the map server.&lt;br /&gt;
* &#039;&#039;map_make_container&#039;&#039;, &#039;&#039;map_container&#039;&#039;, and &#039;&#039;map_container_ns&#039;&#039; - declared as container-related options. The current launch creates a container named &#039;&#039;_map&#039;&#039; with prefix/namespace &#039;&#039;map&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
The map server is configured with:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;topic_name=/loc/map&#039;&#039;&lt;br /&gt;
* &#039;&#039;frame_id=map&#039;&#039;&lt;br /&gt;
* &#039;&#039;yaml_filename=&amp;quot;&amp;quot;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
In the current system, the map is primarily produced by RTAB-Map, while the Nav2 map server exposes map-server functionality for the navigation stack.&lt;br /&gt;
&lt;br /&gt;
=== launch/planning.launch.py ===&lt;br /&gt;
&lt;br /&gt;
Starts the Nav2 planning and navigation servers, the Fejemis velocity relay, the brush service, and optionally the OpenNav coverage server.&lt;br /&gt;
&lt;br /&gt;
Launch arguments:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;namespace&#039;&#039; - namespace for Nav2 nodes. Default: &#039;&#039;fejemis&#039;&#039;.&lt;br /&gt;
* &#039;&#039;use_sim_time&#039;&#039; - forwarded to nodes. Default: &#039;&#039;False&#039;&#039;.&lt;br /&gt;
* &#039;&#039;enable_coverage&#039;&#039; - enables &#039;&#039;opennav_coverage&#039;&#039;. The default is detected from whether the coverage packages are installed.&lt;br /&gt;
&lt;br /&gt;
Nodes:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;nav2_controller/controller_server&#039;&#039; named &#039;&#039;controller&#039;&#039;.&lt;br /&gt;
* &#039;&#039;fejemis_maploc/nav2_cmd_vel_deg_relay.py&#039;&#039;.&lt;br /&gt;
* &#039;&#039;nav2_behaviors/behavior_server&#039;&#039; named &#039;&#039;behavior&#039;&#039;.&lt;br /&gt;
* &#039;&#039;nav2_planner/planner_server&#039;&#039; named &#039;&#039;planner_server&#039;&#039;.&lt;br /&gt;
* &#039;&#039;nav2_bt_navigator/bt_navigator&#039;&#039; named &#039;&#039;bt_navigator&#039;&#039;.&lt;br /&gt;
* &#039;&#039;nav2_map_server/map_server&#039;&#039;.&lt;br /&gt;
* &#039;&#039;fejemis_maploc/brush_control_service.py&#039;&#039; in namespace &#039;&#039;control/brush&#039;&#039;, named &#039;&#039;controller&#039;&#039;.&lt;br /&gt;
* &#039;&#039;opennav_coverage/opennav_coverage&#039;&#039; named &#039;&#039;coverage_server&#039;&#039;, when enabled.&lt;br /&gt;
&lt;br /&gt;
Important remappings:&lt;br /&gt;
&lt;br /&gt;
* Nav2 odometry is remapped from &#039;&#039;/behavior/odom&#039;&#039; to &#039;&#039;/loc/odom&#039;&#039;.&lt;br /&gt;
* Nav2 map input is remapped from &#039;&#039;/behavior/map&#039;&#039; to &#039;&#039;/loc/map&#039;&#039;.&lt;br /&gt;
* Nav2 controller output &#039;&#039;cmd_vel&#039;&#039; is remapped to &#039;&#039;/control/nav2/cmd_vel&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
The velocity relay then converts angular velocity units and republishes to &#039;&#039;/control/joy/cmd_vel&#039;&#039; for the existing Fejemis control path.&lt;br /&gt;
&lt;br /&gt;
The Nav2 configuration in &#039;&#039;config/nav2_servers.yaml&#039;&#039; uses:&lt;br /&gt;
&lt;br /&gt;
* Regulated Pure Pursuit as the path follower.&lt;br /&gt;
* Navfn as the global planner.&lt;br /&gt;
* Local costmap sources from LiDAR, camera scan, net scan, and cable scan.&lt;br /&gt;
* Global costmap with static map, LiDAR obstacle layer, and inflation layer.&lt;br /&gt;
* The OpenNav coverage navigator when coverage support is enabled.&lt;br /&gt;
&lt;br /&gt;
=== launch/lifecycle.launch.py ===&lt;br /&gt;
&lt;br /&gt;
Starts Nav2 lifecycle managers.&lt;br /&gt;
&lt;br /&gt;
Launch arguments:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;in_sim&#039;&#039; - forwarded as &#039;&#039;use_sim_time&#039;&#039;.&lt;br /&gt;
* &#039;&#039;enable_loc_lfc&#039;&#039; - controls whether the localisation lifecycle manager is started. Default: &#039;&#039;false&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
Lifecycle managers:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;/fejemis/beh_lfc&#039;&#039; - behaviour/navigation lifecycle manager.&lt;br /&gt;
* &#039;&#039;/loc/loc_lfc&#039;&#039; - localisation lifecycle manager, only when &#039;&#039;enable_loc_lfc=true&#039;&#039;.&lt;br /&gt;
* &#039;&#039;/fejemis/hard_lfc&#039;&#039; - hardware lifecycle manager, disabled in simulation.&lt;br /&gt;
&lt;br /&gt;
Diagnostics are remapped to &#039;&#039;/diag/beh_lfc&#039;&#039;, &#039;&#039;/diag/loc_lfc&#039;&#039;, and &#039;&#039;/diag/hard_lfc&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
=== launch/lifecycle_managers_only.launch.py ===&lt;br /&gt;
&lt;br /&gt;
Starts the navigation-side launch files without the heavy sensor components. This is useful when another machine is responsible for LiDAR, RealSense, or RTAB-Map.&lt;br /&gt;
&lt;br /&gt;
Launch arguments:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;in_sim&#039;&#039; - enables simulation time.&lt;br /&gt;
* &#039;&#039;localization&#039;&#039; - when true, starts the planning stack on the main Pi. Default: &#039;&#039;false&#039;&#039;.&lt;br /&gt;
* &#039;&#039;enable_coverage&#039;&#039; - enables coverage navigation. Default: &#039;&#039;true&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
Included launch files:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;planning.launch.py&#039;&#039; is included only when &#039;&#039;in_sim=true&#039;&#039; or &#039;&#039;localization=true&#039;&#039;.&lt;br /&gt;
* &#039;&#039;lifecycle.launch.py&#039;&#039; is included immediately.&lt;br /&gt;
* &#039;&#039;behavior.launch.py&#039;&#039; is included after a 30 second delay, giving Nav2 servers time to become active before the BT engine connects to action servers.&lt;br /&gt;
&lt;br /&gt;
=== launch/behavior.launch.py ===&lt;br /&gt;
&lt;br /&gt;
Starts the Fejemis behaviour tree engine from &#039;&#039;raubase_core&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
Launch argument:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;use_sim_time&#039;&#039; - forwarded to the BT engine.&lt;br /&gt;
&lt;br /&gt;
The launch file selects the behaviour tree XML based on the ROS distribution:&lt;br /&gt;
&lt;br /&gt;
* Older BehaviourTree.CPP format: &#039;&#039;resources/behavior/main.xml&#039;&#039;.&lt;br /&gt;
* BehaviourTree.CPP v4 format: &#039;&#039;resources/behavior/main4.xml&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
The node launched is:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;raubase_core/bt_engine&#039;&#039;, configured by &#039;&#039;config/bt_engine.yaml&#039;&#039; in namespace &#039;&#039;fejemis&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
The BT plugin configuration loads:&lt;br /&gt;
&lt;br /&gt;
* raubase logging, mission, blackboard, coverage, and mixer plugins.&lt;br /&gt;
* Nav2 action plugins for single trigger, follow path, drive on heading, spin, and navigate to pose.&lt;br /&gt;
* OpenNav coverage path computation plugin.&lt;br /&gt;
* The package&#039;s custom &#039;&#039;fejemis_call_trigger_service_bt_node&#039;&#039; plugin.&lt;br /&gt;
&lt;br /&gt;
=== launch/robot.launch.py inclusion ===&lt;br /&gt;
&lt;br /&gt;
The top-level &#039;&#039;fejemis&#039;&#039; package includes &#039;&#039;fejemis_maploc&#039;&#039; from &#039;&#039;fejemis/launch/robot.launch.py&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
Typical distribution:&lt;br /&gt;
&lt;br /&gt;
* Main Pi: bridge control, odometry, lifecycle managers, behaviour tree, and planning when localisation is enabled.&lt;br /&gt;
* Auxiliary Pi: LiDAR, RTAB-Map, RealSense, and optional vision.&lt;br /&gt;
&lt;br /&gt;
The top-level launch waits 3 seconds after bridge startup before launching the rest of the stack.&lt;br /&gt;
&lt;br /&gt;
== Script Nodes ==&lt;br /&gt;
&lt;br /&gt;
=== nav2_cmd_vel_deg_relay.py ===&lt;br /&gt;
&lt;br /&gt;
Purpose: adapts Nav2 velocity commands to the Fejemis control convention.&lt;br /&gt;
&lt;br /&gt;
Subscriptions:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;/control/nav2/cmd_vel&#039;&#039; - &#039;&#039;geometry_msgs/Twist&#039;&#039;, with angular velocity in rad/s.&lt;br /&gt;
&lt;br /&gt;
Publications:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;/control/joy/cmd_vel&#039;&#039; - &#039;&#039;geometry_msgs/Twist&#039;&#039;, with angular velocity converted to deg/s.&lt;br /&gt;
&lt;br /&gt;
The linear fields are copied unchanged. The angular x, y, and z fields are multiplied by 180/pi. This is needed because Nav2 uses the normal ROS convention of rad/s, while the existing Fejemis bridge control path expects angular velocity in deg/s.&lt;br /&gt;
&lt;br /&gt;
=== brush_control_service.py ===&lt;br /&gt;
&lt;br /&gt;
Purpose: exposes simple ROS services for turning the brush on and off.&lt;br /&gt;
&lt;br /&gt;
Parameters:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;manage_topic&#039;&#039; - topic for &#039;&#039;fejemis_bridge/msg/SteerManage&#039;&#039;. Default: &#039;&#039;/fejemis/front/manage&#039;&#039;.&lt;br /&gt;
* &#039;&#039;qos_depth&#039;&#039; - publisher QoS depth. Default: &#039;&#039;10&#039;&#039;.&lt;br /&gt;
* &#039;&#039;initial_brush_on&#039;&#039; - initial internal state. Default: &#039;&#039;False&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
Publications:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;manage_topic&#039;&#039; - publishes &#039;&#039;SteerManage.BRUSH_ON&#039;&#039; or &#039;&#039;SteerManage.BRUSH_OFF&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
Services:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;~/set&#039;&#039; - &#039;&#039;std_srvs/SetBool&#039;&#039;, true means brush on and false means brush off.&lt;br /&gt;
* &#039;&#039;~/on&#039;&#039; - &#039;&#039;std_srvs/Trigger&#039;&#039;, turns the brush on.&lt;br /&gt;
* &#039;&#039;~/off&#039;&#039; - &#039;&#039;std_srvs/Trigger&#039;&#039;, turns the brush off.&lt;br /&gt;
&lt;br /&gt;
In &#039;&#039;planning.launch.py&#039;&#039; this node runs as &#039;&#039;/control/brush/controller&#039;&#039;, which creates the services used by the behaviour tree:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;/control/brush/controller/on&#039;&#039;&lt;br /&gt;
* &#039;&#039;/control/brush/controller/off&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
=== rviz_goal_relay.py ===&lt;br /&gt;
&lt;br /&gt;
Purpose: lets an operator send a Nav2 goal from RViz.&lt;br /&gt;
&lt;br /&gt;
Subscriptions:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;/goal_pose&#039;&#039; - RViz 2D Nav Goal as &#039;&#039;geometry_msgs/PoseStamped&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
Action clients:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;/fejemis/navigate_to_pose&#039;&#039; - &#039;&#039;nav2_msgs/action/NavigateToPose&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
When a goal is clicked in RViz, the node checks that the action server is ready, forwards the goal, and logs whether it was accepted and whether it succeeded.&lt;br /&gt;
&lt;br /&gt;
=== mission_trigger.py ===&lt;br /&gt;
&lt;br /&gt;
Purpose: starts a cleaning mission by publishing blackboard values and then setting the active mission.&lt;br /&gt;
&lt;br /&gt;
Parameters:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;mission&#039;&#039; - mission string to publish. Default: &#039;&#039;cleaning/waypoint&#039;&#039;.&lt;br /&gt;
* &#039;&#039;dock_pose&#039;&#039; - dock pose as &#039;&#039;[x, y, yaw]&#039;&#039;. Default: &#039;&#039;[2.62, 3.9, 0.0]&#039;&#039;.&lt;br /&gt;
* &#039;&#039;waypoint_pose&#039;&#039; - waypoint pose as &#039;&#039;[x, y, yaw]&#039;&#039;. Default: &#039;&#039;[2.10491, 0.497438, 3.11127]&#039;&#039;.&lt;br /&gt;
* &#039;&#039;clean_poly&#039;&#039; - polygon string used for cleaning area blackboard values.&lt;br /&gt;
* &#039;&#039;startup_delay&#039;&#039; - delay before publishing. Default: &#039;&#039;2.0&#039;&#039; seconds.&lt;br /&gt;
&lt;br /&gt;
Publications:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;/blackboard/dock_x&#039;&#039;, &#039;&#039;/blackboard/dock_y&#039;&#039;, &#039;&#039;/blackboard/dock_yaw&#039;&#039;&lt;br /&gt;
* &#039;&#039;/blackboard/waypoint_x&#039;&#039;, &#039;&#039;/blackboard/waypoint_y&#039;&#039;, &#039;&#039;/blackboard/waypoint_yaw&#039;&#039;&lt;br /&gt;
* &#039;&#039;/blackboard/field_polygon&#039;&#039;&lt;br /&gt;
* &#039;&#039;/blackboard/clean_poly&#039;&#039;&lt;br /&gt;
* &#039;&#039;/blackboard/start_pose&#039;&#039;&lt;br /&gt;
* &#039;&#039;/blackboard/dock_pose&#039;&#039;&lt;br /&gt;
* &#039;&#039;/blackboard/waypoint_pose&#039;&#039;&lt;br /&gt;
* &#039;&#039;/behavior/set_mission&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The blackboard publishers use transient-local QoS so late subscribers can still receive the latest values. Pose strings are encoded as:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
stamp;frame;x;y;z;qx;qy;qz;qw&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The node also subscribes to &#039;&#039;/behavior/set_mission&#039;&#039;. When it sees &#039;&#039;auto/idle&#039;&#039; after the mission has run, it logs completion and shuts down.&lt;br /&gt;
&lt;br /&gt;
=== complete_coverage_planner.py ===&lt;br /&gt;
&lt;br /&gt;
Purpose: interactive coverage test node for RViz.&lt;br /&gt;
&lt;br /&gt;
Parameters:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;rviz_click_topic&#039;&#039; - clicked point topic. Default: &#039;&#039;/clicked_point&#039;&#039;.&lt;br /&gt;
* &#039;&#039;coverage_frame&#039;&#039; - frame for the coverage polygon. Default: &#039;&#039;map&#039;&#039;.&lt;br /&gt;
* &#039;&#039;min_polygon_points&#039;&#039; - number of clicked points before sending a goal. Default: &#039;&#039;4&#039;&#039;.&lt;br /&gt;
* &#039;&#039;auto_close_polygon&#039;&#039; - whether to append the first point as the final point. Default: &#039;&#039;True&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
Subscriptions:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;/clicked_point&#039;&#039; by default - RViz point clicks.&lt;br /&gt;
&lt;br /&gt;
Action clients:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;fejemis/navigate_complete_coverage&#039;&#039; - &#039;&#039;opennav_coverage_msgs/action/NavigateCompleteCoverage&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
The node waits for &#039;&#039;fejemis/bt_navigator&#039;&#039; to become active, collects clicked points in RViz, converts them to a &#039;&#039;geometry_msgs/Polygon&#039;&#039;, and sends a coverage navigation action once enough points have been collected.&lt;br /&gt;
&lt;br /&gt;
=== fixed_complete_coverage_planner.py ===&lt;br /&gt;
&lt;br /&gt;
Purpose: fixed-polygon coverage test node.&lt;br /&gt;
&lt;br /&gt;
Parameters:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;coverage_frame&#039;&#039; - frame for the coverage polygon. Default: &#039;&#039;map&#039;&#039;.&lt;br /&gt;
* &#039;&#039;min_polygon_points&#039;&#039; - retained from the interactive tester. Default: &#039;&#039;4&#039;&#039;.&lt;br /&gt;
* &#039;&#039;auto_close_polygon&#039;&#039; - whether to append the first point as the final point. Default: &#039;&#039;True&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
Action clients:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;fejemis/navigate_complete_coverage&#039;&#039; - &#039;&#039;opennav_coverage_msgs/action/NavigateCompleteCoverage&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
The node waits for &#039;&#039;fejemis/bt_navigator&#039;&#039; to become active, then sends a fixed four-point polygon:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
(-5.35,  2.45)&lt;br /&gt;
( 2.83,  3.18)&lt;br /&gt;
( 3.22, -5.08)&lt;br /&gt;
(-4.76, -5.97)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This is useful for testing coverage planning without clicking points manually in RViz.&lt;br /&gt;
&lt;br /&gt;
== C++ Behaviour Tree Plugin ==&lt;br /&gt;
&lt;br /&gt;
=== fejemis_call_trigger_service_bt_node ===&lt;br /&gt;
&lt;br /&gt;
The C++ plugin &#039;&#039;src/bt_plugins/call_trigger_service_bt_node.cpp&#039;&#039; registers the behaviour tree node &#039;&#039;CallTriggerService&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
Inputs:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;service_name&#039;&#039; - service to call.&lt;br /&gt;
* &#039;&#039;wait_for_service_ms&#039;&#039; - timeout while waiting for service availability. Default: &#039;&#039;1000&#039;&#039;.&lt;br /&gt;
* &#039;&#039;call_timeout_ms&#039;&#039; - timeout for the service call. Default: &#039;&#039;2000&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
The node creates a &#039;&#039;std_srvs/Trigger&#039;&#039; client, waits for the target service, calls it, and returns:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;SUCCESS&#039;&#039; when the service exists, responds before timeout, and returns &#039;&#039;success=true&#039;&#039;.&lt;br /&gt;
* &#039;&#039;FAILURE&#039;&#039; when the service is unavailable, the call times out, or the response reports failure.&lt;br /&gt;
&lt;br /&gt;
The cleaning subtree uses it to call:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;/control/brush/controller/on&#039;&#039;&lt;br /&gt;
* &#039;&#039;/control/brush/controller/off&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
== Behaviour Tree ==&lt;br /&gt;
&lt;br /&gt;
The active behaviour tree on newer ROS distributions is &#039;&#039;resources/behavior/main4.xml&#039;&#039;. It is loaded by &#039;&#039;behavior.launch.py&#039;&#039; through the &#039;&#039;raubase_core/bt_engine&#039;&#039; node.&lt;br /&gt;
&lt;br /&gt;
=== Main Loop ===&lt;br /&gt;
&lt;br /&gt;
The root tree is &#039;&#039;main&#039;&#039;:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
main&lt;br /&gt;
`-- Sequence&lt;br /&gt;
    |-- SingleTrigger&lt;br /&gt;
    |   `-- t_init&lt;br /&gt;
    `-- KeepRunningUntilFailure&lt;br /&gt;
        `-- ReactiveSequence&lt;br /&gt;
            |-- ForceSuccess&lt;br /&gt;
            |   `-- t_passive&lt;br /&gt;
            `-- t_runtime&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The structure means:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;t_init&#039;&#039; runs once at startup.&lt;br /&gt;
* &#039;&#039;t_passive&#039;&#039; runs repeatedly and updates blackboard/mission state.&lt;br /&gt;
* &#039;&#039;t_runtime&#039;&#039; selects the active behaviour based on the current mission.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Visualization placeholder:&#039;&#039;&#039; exported Groot image of &#039;&#039;main4.xml&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
=== t_init ===&lt;br /&gt;
&lt;br /&gt;
Initialises the robot behaviour state:&lt;br /&gt;
&lt;br /&gt;
* Sets mission to &#039;&#039;auto/idle&#039;&#039;.&lt;br /&gt;
* Sets blackboard key &#039;&#039;initialized=1&#039;&#039;.&lt;br /&gt;
* Sets calibration square distance &#039;&#039;calib_sq_dist=7&#039;&#039;.&lt;br /&gt;
* Opens the mixer port using &#039;&#039;OpenMixerPort&#039;&#039; with priority 4 and topic &#039;&#039;/behavior&#039;&#039;.&lt;br /&gt;
* Logs that the robot is initialized.&lt;br /&gt;
&lt;br /&gt;
=== t_passive ===&lt;br /&gt;
&lt;br /&gt;
Runs as the passive safety/update loop:&lt;br /&gt;
&lt;br /&gt;
* Logs &#039;&#039;Safety loop&#039;&#039; at debug level.&lt;br /&gt;
* Refreshes &#039;&#039;start_pose&#039;&#039;, &#039;&#039;waypoint_pose&#039;&#039;, &#039;&#039;clean_poly&#039;&#039;, and &#039;&#039;field_polygon&#039;&#039; from &#039;&#039;/blackboard/...&#039;&#039; topics.&lt;br /&gt;
* Refreshes the current mission from &#039;&#039;/behavior/set_mission&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
=== t_runtime ===&lt;br /&gt;
&lt;br /&gt;
Selects behaviour using a reactive fallback:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
t_runtime&lt;br /&gt;
|-- t_idle_runtime&lt;br /&gt;
|-- t_calib&lt;br /&gt;
|-- t_mapping&lt;br /&gt;
|-- t_cleaning&lt;br /&gt;
`-- unknown mission handler -&amp;gt; set idle&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The first matching mission branch runs. If no known branch matches, the tree logs an error and returns to idle.&lt;br /&gt;
&lt;br /&gt;
=== Idle ===&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;t_idle_runtime&#039;&#039; succeeds when the current mission is:&lt;br /&gt;
&lt;br /&gt;
* mission: &#039;&#039;auto&#039;&#039;&lt;br /&gt;
* mission_sub: &#039;&#039;idle&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
This keeps the robot in a waiting state. Sensors, telemetry, and subscriptions remain active, but no movement commands are issued. The robot leaves this state when a new mission is received through &#039;&#039;/behavior/set_mission&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
=== Calibration ===&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;t_calib&#039;&#039; chooses between line calibration and square calibration.&lt;br /&gt;
&lt;br /&gt;
Line calibration, &#039;&#039;t_calib_line&#039;&#039;:&lt;br /&gt;
&lt;br /&gt;
* Requires mission &#039;&#039;calib/line&#039;&#039;.&lt;br /&gt;
* Drives forward using &#039;&#039;DriveOnHeading&#039;&#039; for &#039;&#039;calib_sq_dist&#039;&#039; at 0.05 m/s.&lt;br /&gt;
* Returns to idle.&lt;br /&gt;
&lt;br /&gt;
Square calibration, &#039;&#039;t_calib_square&#039;&#039;:&lt;br /&gt;
&lt;br /&gt;
* Requires mission &#039;&#039;calib/*&#039;&#039;.&lt;br /&gt;
* Runs &#039;&#039;f_calib_side&#039;&#039; four times.&lt;br /&gt;
* Each side drives forward and spins 1.57 rad.&lt;br /&gt;
* Returns to idle.&lt;br /&gt;
&lt;br /&gt;
=== Mapping ===&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;t_mapping&#039;&#039; currently checks for mission &#039;&#039;mapping/*&#039;&#039; and then returns to idle. This branch is a placeholder for future autonomous mapping behaviour.&lt;br /&gt;
&lt;br /&gt;
=== Cleaning ===&lt;br /&gt;
&lt;br /&gt;
The current cleaning branch is &#039;&#039;t_cleaning&#039;&#039;:&lt;br /&gt;
&lt;br /&gt;
* Requires mission &#039;&#039;cleaning/waypoint&#039;&#039; with mission_sub &#039;&#039;idle&#039;&#039;.&lt;br /&gt;
* Navigates to &#039;&#039;{waypoint_pose}&#039;&#039; using &#039;&#039;NavigateToPose&#039;&#039;.&lt;br /&gt;
* Sets current mission to &#039;&#039;cleaning/area&#039;&#039;.&lt;br /&gt;
* Returns to idle.&lt;br /&gt;
&lt;br /&gt;
Supporting cleaning subtrees are already present:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;f_get_area_to_clean&#039;&#039; sets a polygon and calls &#039;&#039;ComputeCoveragePath&#039;&#039;.&lt;br /&gt;
* &#039;&#039;f_clean_area&#039;&#039; turns the brush on, follows &#039;&#039;{cover_nav_path}&#039;&#039;, and turns the brush off.&lt;br /&gt;
* &#039;&#039;f_go_to_path_start&#039;&#039; is a placeholder that logs navigation to the path start.&lt;br /&gt;
&lt;br /&gt;
These subtrees show the intended full cleaning sequence, although the active &#039;&#039;t_cleaning&#039;&#039; branch currently only navigates to a waypoint and changes mission state.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Diagram placeholder:&#039;&#039;&#039; cleaning behaviour sequence: mission trigger -&amp;gt; waypoint navigation -&amp;gt; coverage path computation -&amp;gt; brush on -&amp;gt; follow coverage path -&amp;gt; brush off -&amp;gt; idle.&lt;br /&gt;
&lt;br /&gt;
== Configuration Files ==&lt;br /&gt;
&lt;br /&gt;
=== config/odometry.yaml and config/odometry_sim.yaml ===&lt;br /&gt;
&lt;br /&gt;
Configure the EKF and IMU filter. The real configuration fuses wheel odometry and RTAB-Map ICP odometry in 2D mode. The simulation configuration is separated to avoid conflicting simulated IMU orientation.&lt;br /&gt;
&lt;br /&gt;
=== config/rtabmap.yaml and config/rtabmap_sim.yaml ===&lt;br /&gt;
&lt;br /&gt;
Configure RTAB-Map for real robot and simulation use. &#039;&#039;rtabmap_slam.launch.py&#039;&#039; selects between them using &#039;&#039;use_sim_time&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
=== config/nav2_servers.yaml and config/nav2_sim.yaml ===&lt;br /&gt;
&lt;br /&gt;
Configure Nav2 controller, planner, behaviour server, costmaps, BT navigator, coverage server, and map server. The main real-robot launch uses &#039;&#039;nav2_servers.yaml&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
=== config/bt_engine.yaml ===&lt;br /&gt;
&lt;br /&gt;
Lists behaviour tree plugins loaded by &#039;&#039;raubase_core/bt_engine&#039;&#039;, including the Fejemis custom trigger-service plugin.&lt;br /&gt;
&lt;br /&gt;
=== config/lifecycle.yaml ===&lt;br /&gt;
&lt;br /&gt;
Configures lifecycle manager node lists and autostart behaviour for the navigation, localisation, and hardware lifecycle managers.&lt;br /&gt;
&lt;br /&gt;
=== config/realsense.yaml ===&lt;br /&gt;
&lt;br /&gt;
RealSense camera configuration loaded by &#039;&#039;realsense.launch.py&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
== Typical Usage ==&lt;br /&gt;
&lt;br /&gt;
Start the complete robot stack through the top-level launch:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ros2 launch fejemis robot.launch.py localization:=true map_location:=asta&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Run on both Raspberry Pis. The launch file decides which parts run on the main and auxiliary computers through the &#039;&#039;is_aux&#039;&#039; argument.&lt;br /&gt;
&lt;br /&gt;
Start a waypoint cleaning mission after the system is up:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ros2 run fejemis_maploc mission_trigger.py&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Send an RViz navigation goal:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ros2 run fejemis_maploc rviz_goal_relay.py&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Test coverage navigation with RViz clicked points:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ros2 run fejemis_maploc complete_coverage_planner.py&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Test coverage navigation with the fixed polygon:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ros2 run fejemis_maploc fixed_complete_coverage_planner.py&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Notes and Future Work ==&lt;br /&gt;
&lt;br /&gt;
* Clarify which Raspberry Pi should run each launch file in the final deployment.&lt;br /&gt;
* Expand the cleaning behaviour section once &#039;&#039;t_cleaning&#039;&#039; is connected to &#039;&#039;f_get_area_to_clean&#039;&#039; and &#039;&#039;f_clean_area&#039;&#039; in the active tree.&lt;/div&gt;</summary>
		<author><name>S252724</name></author>
	</entry>
</feed>