The problem
Moving through a room involves a sequence of decisions: choosing a destination, finding a clear route, avoiding obstacles, and adjusting as the surroundings change. For a wheelchair user, managing that movement can require sustained physical effort or help from another person.
Guido explores how voice control and autonomous navigation can support that process. We built a small-scale platform that accepts spoken movement commands, maps its surroundings with LiDAR, and navigates to waypoints. Exploring an unfamiliar room also lets it build the map it needs for later journeys.
My contribution
I developed frontier-based exploration and mapping, designed the modular SLAM, Nav2, and supervisor architecture, and helped integrate the Jetson, LiDAR, Arduino, and motion platform. The working prototype brought together our team’s hardware, voice, and navigation work.
Architecture
The architecture separates command interpretation, supervision, navigation, and motor control into ROS 2 nodes. A command describes the intended action. The supervisor checks the operating mode, available map, robot pose, and sensor health before issuing a navigation request. Nav2 handles route execution, while the serial bridge translates velocity commands for the motor controller.
Mapping supplies the spatial context for these decisions. SLAM Toolbox combines LiDAR observations and the robot’s estimated motion to maintain the occupancy map and pose. The exploration planner consumes that map, chooses a reachable frontier, and passes the resulting goal through the navigation layer.
| Component | Role |
|---|---|
| NVIDIA Jetson Orin Nano | Onboard compute for perception, commands, and autonomy. |
| LD19 LiDAR | Distance measurements around the platform for mapping and obstacle detection. |
| Arduino and motor driver | Low-level motor commands over the serial connection. |
| Encoders and MPU6050 | Wheel-motion and inertial feedback for the prototype. |
Mapping and exploration
LiDAR measures the distance to surfaces around the robot. The scan appears as a set of returns in the visualization below. As Guido moves, SLAM Toolbox aligns successive scans with the robot’s estimated motion to build an occupancy map.

An occupancy grid represents the room as free, occupied, and unknown cells. Guido finds unknown cells next to free space and groups connected boundary cells into frontier clusters. The minimum cluster size removes small fragments that would otherwise create many weak exploration candidates.
The code below detects that boundary in the occupancy map. A value of −1 marks an unknown cell; 0 marks free space. The helper checks all eight neighboring cells, and the planner groups the detected boundary cells into connected clusters.
def _find_frontier_cells(grid: OccupancyGridSnapshot) -> set[Tuple[int, int]]:
cells = set()
for row in range(grid.height):
for col in range(grid.width):
if grid.value(row, col) != -1:
continue
if _has_adjacent_free_cell(grid, row, col):
cells.add((row, col))
return cells
def _has_adjacent_free_cell(grid: OccupancyGridSnapshot, row: int, col: int) -> bool:
for row_offset, col_offset in _NEIGHBORS_8:
neighbor_row = row + row_offset
neighbor_col = col + col_offset
if not grid.in_bounds(neighbor_row, neighbor_col):
continue
if grid.value(neighbor_row, neighbor_col) == 0:
return True
return FalseA frontier identifies an area worth observing. The actual navigation goal is a reachable free cell alongside that cluster. The planner searches the free space connected to the robot, filters candidates near blacklisted frontiers, and selects a suitable approach point.
Candidate ranking combines travel cost and information gain. The implementation estimates travel cost with A*, normalizes distance and gain across the available candidates, and applies a configurable weight. Lower scores are preferred. The default information-gain weight is 0.3.
− weight × normalized information gain
During exploration, a rejected or timed-out goal is added to a blacklist so the next planning cycle can choose another frontier. The supervisor also tracks empty planning cycles and supports a coverage threshold for ending exploration. Goal timeouts scale with estimated travel distance, expected speed, and a safety factor.
Voice and motion control
The voice pipeline turns speech into text using Vosk or ElevenLabs. A transcript bridge filters partial results and normalizes wake words and destination aliases. The agent and command layers interpret that request, while the supervisor maintains navigation state and checks whether the action can proceed.
Fresh sensor data is part of that decision. The health monitor tracks the most recent LiDAR scan, odometry, and transform updates. Its defaults allow 0.5 seconds for scan freshness, 2 seconds for odometry, and 1 second for TF. In exploration mode, a failed health check clears the active exploration state and emits a stop action.
if self._exploration_active:
health_status = self._health_monitor.status(now=current_time)
if not health_status.healthy:
self._exploration_active = False
self._empty_frontier_cycles = 0
self._pending_map_save = None
self._map_save_in_progress = None
self._active_goal = None
return CommandResponse(
accepted=False,
message="Exploration aborted: {}.".format(health_status.details),
actions=(
SupervisorAction(
action_type=ActionType.STOP,
reason="exploration aborted due to stale health",
),
),
)The serial bridge provides another timeout at the motion boundary. It tracks the age of the latest velocity command and writes STOP when that age exceeds its configured limit, which defaults to 0.5 seconds. This gives the motor interface a direct response to interrupted command delivery.
These layers address different points in the command path: sensor freshness before and during exploration, goal timeouts during navigation, and command freshness at the serial bridge. Keeping those responsibilities explicit made integration and debugging easier as the hardware came together.
Results
Guido won Most Innovative Hack at HackUSF 2026 among more than 280 projects. The team demonstrated a working small-scale platform with voice-driven motion, LiDAR mapping, waypoint navigation, and obstacle avoidance.
For this case study, I ran 27 existing frontier and supervisor checks against the pinned source. They cover cluster extraction, reachable goals, blacklisting, command prerequisites, timeouts, and stale-sensor handling. Figure 3 follows a deterministic supervisor fixture.
| Check | Observed behavior |
|---|---|
| Reachable goal selection | The selected approach point lies in connected free space. |
| Failed frontier handling | Rejected and timed-out frontiers enter the blacklist before replanning. |
| Stale exploration data | The supervisor clears exploration state and emits STOP. |
| Navigation timeout | An expired navigation goal produces a stop action. |
The opening scene is a separate illustrative room with a fixed route and simulated LiDAR rays. It provides a direct way to explore the spatial ideas behind Guido. The demonstration below shows the physical prototype.