Making the system robust with Fallbacks¶
In the previous Point Navigation tutorial, we built a standard point navigation stack. While functional, it is “optimistic”, it assumes that all the components will keep working smoothly and no errors or failures would happen during runtime.
In reality, sensors disconnect, optimization solvers fail to converge, and dynamic obstacles block paths.
In this tutorial, we will give our Turtlebot3 a few Fallback techniques, so the robot can attempt to self-heal and reconfigure itself when these inevitable failures occur.
Handling Algorithm Failures¶
Algorithms used inside the navigation components can fail during runtime for many reasons. A motion control algorithm used in the Controller can fail, for example, due to:
Mathematical Singularity: The optimization solver might fail to find a solution that satisfies all kinematic constraints (e.g., the robot is cornered).
Local Minima: The robot might get “stuck” oscillating in a narrow corridor or doorway.
Tracking Error: The robot has drifted too far from the path, and the controller cannot compute a valid velocity to rejoin it smoothly.
Implementation
If our primary high-performance algorithm (e.g., DWA) fails, we shouldn’t just stop. We can switch to a “Safety” algorithm (like a simple PurePursuit).
Start by importing the
Actionclass required to define the Fallback routines, and theControllersIDenum for selecting our main controller and our fallback controller:
from kompass.ros import Action
from kompass.control import ControllersID
Select the primary control algorithm, and define the
Actionto switch the algorithm into our fallback choice:
# Select the primary control algorithm
controller.algorithm = ControllersID.DWA
# Define the action
switch_algorithm_action = Action(method=controller.set_algorithm, args=(ControllersID.PURE_PURSUIT,))
Set the fallbacks action sequence: Try restarting the controller -> If fails again -> Switch the control algorithm
controller.on_algorithm_fail(action=[Action(controller.restart), switch_algorithm_action], max_retries=1)
Handling System Failures¶
Some component, like the DriveManager talks directly to the low-level hardware (micro-controller/motor drivers). And in rel-life the robot can face different system failures during runtime:
Cable Issues: Loose USB/Serial cables caused by vibration.
EMI: Electromagnetic interference causing packet loss on the serial bus.
Watchdog Trip: The micro-controller resets itself because it didn’t receive a command in time.
Implementation¶
To make our system more resilient, we can implement a simple ‘restart’ fallback for system failures. Since hardware glitches are often transient, this simple restart fallback can re-establishes the serial handshake and be quite effective.
We can do this by simply adding the following line to our recipe:
# Not setting max_retires -> will attempt restarting infinity
driver.on_system_fail(Action(driver.restart))
We can also select a general component-level fallback for all our component by doing:
launcher.on_fail(action_name="restart")
Complete Recipe¶
Here is the complete turtlebot3_with_fallbacks.py script combining the Point Navigation setup with our new robustness layer.
1import numpy as np
2from kompass.robot import (
3 AngularCtrlLimits,
4 LinearCtrlLimits,
5 RobotGeometry,
6 RobotType,
7)
8from kompass.config import RobotConfig, RobotFrames
9from kompass.components import (
10 Controller,
11 DriveManager,
12 Planner,
13 PlannerConfig,
14)
15from kompass.control import ControllersID
16from kompass.ros import Topic, Launcher, Action
17
18# Setup your robot configuration
19my_robot = RobotConfig(
20 model_type=RobotType.DIFFERENTIAL_DRIVE,
21 geometry_type=RobotGeometry.Type.CYLINDER,
22 geometry_params=np.array([0.1, 0.3]),
23 ctrl_vx_limits=LinearCtrlLimits(max_vel=0.2, max_acc=1.5, max_decel=2.5),
24 ctrl_omega_limits=AngularCtrlLimits(
25 max_vel=0.4, max_acc=2.0, max_decel=2.0, max_steer=np.pi / 3)
26)
27
28# Set the robot frames
29robot_frames = RobotFrames(
30 robot_base='base_link',
31 odom='odom',
32 world='map',
33 scan='LDS-01')
34
35# Setup components with default config, inputs and outputs
36planner_config = PlannerConfig(loop_rate=1.0) # 1 Hz
37planner = Planner(component_name="planner", config=planner_config)
38
39# Set Planner goal input to Rviz Clicked point
40goal_topic = Topic(name="/clicked_point", msg_type="PointStamped")
41planner.inputs(goal_point=goal_topic)
42
43# Get a default controller component
44controller = Controller(component_name="controller")
45
46# Configure Controller to use local map instead of direct sensor information
47controller.direct_sensor = False
48
49# Select the primary control algorithm
50controller.algorithm = ControllersID.DWA
51
52# Define the action
53switch_algorithm_action = Action(method=controller.set_algorithm, args=(ControllersID.PURE_PURSUIT,))
54
55# Set the Controller 'algorithm-level' failure fallback
56controller.on_algorithm_fail(action=[Action(controller.restart), switch_algorithm_action], max_retries=1)
57
58# Get the default DriveManager
59driver = DriveManager(component_name="drive_manager")
60
61# Publish Twist or TwistStamped from the DriveManager based on the distribution
62if "ROS_DISTRO" in os.environ and (
63 os.environ["ROS_DISTRO"] in ["rolling", "jazzy", "kilted"]
64):
65 cmd_msg_type : str = "TwistStamped"
66else:
67 cmd_msg_type = "Twist"
68
69driver.outputs(robot_command=Topic(name="/cmd_vel", msg_type=cmd_msg_type))
70
71# Set the DriveManager System-Level failure Fallback
72# Not setting max_retires -> will attempt restarting infinity
73driver.on_system_fail(Action(driver.restart))
74
75# Get a default Local Mapper component
76mapper = LocalMapper(component_name="mapper")
77
78# Init a launcher
79launcher = Launcher()
80
81# Pass kompass components to the launcher
82launcher.kompass(
83 components=[planner, controller, driver, mapper],
84 activate_all_components_on_start=True,
85 multi_processing=True)
86
87# Set the robot
88launcher.robot = robot_config
89
90# Set the frames
91launcher.frames = frames_config
92
93# Optional Generic Fallback Policy: If any component fails -> restart it with unlimited retries
94# launcher.on_fail(action_name="restart")
95
96# After all configuration is done bringup the stack
97launcher.bringup()
Next Steps¶
Your robot can now heal itself from internal failures! But what about reacting to the external world?
In the next tutorial, we will use Events to make the robot reactive to its environment.