Skip to content

feat(patrol): add patrolling module#1488

Open
paul-nechifor wants to merge 5 commits intodevfrom
paul/feat/patrol
Open

feat(patrol): add patrolling module#1488
paul-nechifor wants to merge 5 commits intodevfrom
paul/feat/patrol

Conversation

@paul-nechifor
Copy link
Contributor

@paul-nechifor paul-nechifor commented Mar 8, 2026

Problem

We need a way to patrol explored areas.

Closes DIM-659

Solution

  • Wrote a few patrolling algorithms, and I'm using coverage by default because it looks the best.

  • Wrote a test to visualize how well it covers the space (all tests are on the Hong Kong office scan from Ivan).:

coverage
patrolling_coverage_coverage
frontier
patrolling_coverage_frontier
random
patrolling_coverage_random
  • Tested the best values for saturation_threshold and clearance_radius_m.
patrol_router_optimization
  • Tested the best values for candidates_to_consider.
candidates_optimization_equal_cells
  • Verified that the robot patrols for a significant while.
patrol_path ignore_03

Breaking Changes

None.

How to Test

uv run dimos run --disable spatial-memory unitree-go2-agentic

and tell it to start patrolling.

Issues

This doesn't always work because you get bad detections such as this (that's actually part of the chair):

debug-2026-03-12T00-26-49 829560+00-00

It messes up the entire flow. We need persistent 3D detections, and maybe a 360 camera.

Contributor License Agreement

  • I have read and approved the CLA.

@greptile-apps
Copy link
Contributor

greptile-apps bot commented Mar 8, 2026

Greptile Summary

This PR introduces a complete patrolling module for the Dimensional OS robot stack, enabling autonomous area patrol via three interchangeable routing strategies (coverage, frontier, random). It wires cleanly into the existing Module / autoconnect framework and is backed by A*-based path planning, Voronoi-weighted candidate sampling, and a bounded-memory visitation history.

Key findings:

  • Missing default case in create_patrol_router — the match statement lacks a case _: branch; an unrecognized router name silently returns None, violating the PatrolRouter return type and causing a downstream AttributeError.
  • Redundant duplicate import of PoseStamped in utilities.py (line 16-17, second shadows the first).

The core patrolling logic is well-structured, thread-safe, and backed by tests. These two issues are straightforward to fix and should be addressed before merge.

Confidence Score: 4/5

  • Safe to merge after addressing the missing default case in the factory function, which currently allows implicit None returns that violate the type annotation.
  • The patrolling module is well-structured with sound core logic and proper threading. The identified issues are straightforward to fix: a missing default case in the factory function (type violation) and a redundant import. The former must be addressed; the latter is minor cleanup. The behavioral change to min_cost_astar is intentional per the PR summary and does not require further action. The core patrolling, routing, and A* logic are robust and well-tested.
  • dimos/navigation/patrolling/create_patrol_router.py (missing default case—must be fixed before merge)

Sequence Diagram

sequenceDiagram
    participant Agent as AI Agent / Skill
    participant PM as PatrollingModule
    participant Router as PatrolRouter (Coverage/Frontier/Random)
    participant Nav as NavigationSystem
    participant ROS as ROS Topics (odom / costmap / goal_reached)

    Agent->>PM: start_patrol()
    PM->>PM: spawn _patrol_thread

    loop Patrol Loop
        PM->>Router: next_goal()
        Router->>Router: sample unvisited safe cells (Voronoi-weighted)
        Router->>Router: run min_cost_astar for each candidate
        Router-->>PM: PoseStamped goal (or None)

        alt goal is None
            PM->>PM: wait 2s or stop_event
        else goal available
            PM->>Nav: goal_request.publish(goal)
            PM->>PM: _goal_or_stop_event.wait()
            Nav-->>ROS: robot navigates
            ROS-->>PM: goal_reached callback
            PM->>PM: _goal_or_stop_event.set()
        end
    end

    ROS-->>PM: odom callback → _latest_pose, Router.handle_odom()
    ROS-->>PM: global_costmap callback → Router.handle_occupancy_grid() (throttled 60s)
    Router->>Router: VisitationHistory.update_grid() + _rebuild()

    Agent->>PM: stop_patrol()
    PM->>PM: _stop_event.set(), _goal_or_stop_event.set()
    PM->>Nav: goal_request.publish(_latest_pose)  [cancel navigation]
    PM->>PM: _patrol_thread.join()
Loading

Last reviewed commit: 1eec5e0

@paul-nechifor paul-nechifor force-pushed the paul/feat/perceive-loop branch from 649db7b to 0e07984 Compare March 9, 2026 11:39
@paul-nechifor paul-nechifor force-pushed the paul/feat/perceive-loop branch 2 times, most recently from ea7a704 to dfb30a0 Compare March 9, 2026 22:40
@paul-nechifor paul-nechifor force-pushed the paul/feat/patrol branch 3 times, most recently from a4a61aa to 7c4e027 Compare March 10, 2026 06:33
@paul-nechifor paul-nechifor changed the base branch from paul/feat/perceive-loop to dev March 10, 2026 06:34
@spomichter spomichter changed the base branch from dev to main March 10, 2026 08:45
@spomichter spomichter changed the base branch from main to dev March 10, 2026 08:45
@spomichter spomichter force-pushed the paul/feat/patrol branch 2 times, most recently from 1bd23c3 to 0182ab6 Compare March 10, 2026 09:00
@leshy
Copy link
Contributor

leshy commented Mar 13, 2026

I'd maybe add some of those nice pictures + a short description on how it works, into docs somewhere? docs/capabilities ?


Args:
query: Description of the person to follow (e.g., "man with blue shirt")
initial_bbox: Optional pre-computed bounding box [x1, y1, x2, y2].
Copy link
Contributor

Choose a reason for hiding this comment

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

this is exactly what Detection2d/bbox model is, should we use those for detections to follow?

I can imagine visual following making sense in many cases, would be good to support standard detection types?



class PatrollingModule(Module):
odom: In[PoseStamped]
Copy link
Contributor

Choose a reason for hiding this comment

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

why not use self.tf?

class PatrollingModule(Module):
odom: In[PoseStamped]
global_costmap: In[OccupancyGrid]
goal_reached: In[Bool]
Copy link
Contributor

@leshy leshy Mar 13, 2026

Choose a reason for hiding this comment

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

this might be a good situation for jeffs RPC in which we feed nav module directly to this module as a dependancy, so it can call rpcs from it? we get nice type level dependance right? I haven't done this yet so slightly unsure

with self._patrol_lock:
if self._patrol_thread is not None and self._patrol_thread.is_alive():
return "Patrol is already running. Use `stop_patrol` to stop."
self._planner_spec.set_replanning_enabled(False)
Copy link
Contributor

Choose a reason for hiding this comment

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

How come? don't we have dynamic env issue if we disable replanning?

Copy link
Contributor Author

@paul-nechifor paul-nechifor Mar 13, 2026

Choose a reason for hiding this comment

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

In normal navigation, when you set a goal, you need to get there. With patrolling, what's important is covering the patrol area, not necessarily getting to a specific point.

When setting a specific random target, there are issues that cause re-planning every once in a while (maybe someone got in the way, maybe some random noise blocked the path, maybe the goal was unreachable to begin with).

Instead of re-planning to get to the arbitrary target, it's a lot faster to just select another arbitrary target. If the goal is unreachable, the robot wastes a lot of time trying to get to a place that it cannot get to.

The router is in charge of making sure enough of the area is covered, so if the goal was actually important to get to, it will try to get there soon enough.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants