Skip to content

Commit f5a2dc3

Browse files
kaiaaiclaude
andcommitted
wall_clean: parameterize cruise arc by radius, not angular rate
Replace the arc_omega (rad/s) parameter with arc_radius (m) and derive the turn rate each tick as omega = v_cruise / arc_radius. Specifying the radius makes the arc shape independent of cruise speed: the robot traces the same path whether it cruises fast or slow. Default arc_radius=1.5 m reproduces the old 0.1 rad/s at the default v_cruise=0.15. Backoff retrace is unaffected (it still uses the arc rate captured at contact). Docs updated. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent ad295b6 commit f5a2dc3

3 files changed

Lines changed: 20 additions & 13 deletions

File tree

docs/wall-follow-bump-out.md

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ A 20 Hz loop cycles three states:
2222

2323
| State | Motion | Leaves when |
2424
|---|---|---|
25-
| **CRUISE** | forward `v_cruise` + gentle **right** arc `-arc_omega` (drifting into the wall) | a bumper fires → BACKOFF |
25+
| **CRUISE** | forward `v_cruise` + gentle **right** arc of radius `arc_radius` (drifting into the wall) | a bumper fires → BACKOFF |
2626
| **BACKOFF** | **backs OUT along the arc it drove in on** (see below), for `backoff_s` | timer elapses → TURN |
2727
| **TURN** | rotate **left** in place at `turn_speed`, by an angle that depends on which bumper hit | angle reached → CRUISE |
2828

@@ -39,7 +39,8 @@ once it's actually following the wall.
3939

4040
## Backing out along the entry arc
4141

42-
CRUISE curves into the wall on an arc (radius `v_cruise/arc_omega`). On contact,
42+
CRUISE curves into the wall on an arc of radius `arc_radius` (turn rate
43+
`v_cruise/arc_radius`). On contact,
4344
BACKOFF **retraces that arc in reverse** rather than reversing in a straight
4445
line. **Why:** retracing keeps the robot on the strip of floor it just drove
4546
through, so pulling out of a contact is far less likely to wedge it somewhere
@@ -72,7 +73,7 @@ retunes the *running* robot with no relaunch — see the
7273
| `clean.<name>` | Default | Meaning |
7374
|---|---|---|
7475
| `v_cruise` | 0.15 | forward cleaning speed (m/s) |
75-
| `arc_omega` | 0.1 | gentle right-arc rate while cruising (rad/s) |
76+
| `arc_radius` | 1.5 | cruise arc radius into the wall (m); turn rate is `v_cruise/arc_radius`, so the arc shape is speed-independent |
7677
| `v_back` | 0.10 | back-out reverse speed (m/s) |
7778
| `backoff_s` | 0.5 | back-out duration (s) → distance `v_back·backoff_s` |
7879
| `turn_speed` | 0.7 | angular speed while turning left (rad/s) |
@@ -88,7 +89,7 @@ set at launch).
8889
# 1. teleop-aim the robot at the wall, then:
8990
ros2 launch oomwoo_clean wall_clean_bump_out.launch.py use_sim_time:=true
9091
# 2. tune live while it runs (persists for next launch too):
91-
kaia set clean.arc_omega 0.15
92+
kaia set clean.arc_radius 1.0
9293
kaia set clean.turn_left_deg 80
9394
```
9495

src/oomwoo_clean/launch/wall_clean_bump_out.launch.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@
4444
# name -> built-in default; each is overridable by kaia (clean.<name>) then :=
4545
CLEAN_PARAMS = {
4646
'v_cruise': 0.15, # forward cleaning speed (m/s)
47-
'arc_omega': 0.1, # gentle right-arc rate while cruising (rad/s)
47+
'arc_radius': 1.5, # cruise arc radius into the wall (m); omega=v_cruise/r
4848
'v_back': 0.10, # backoff reverse speed (m/s)
4949
'backoff_s': 0.5, # backoff duration (s)
5050
'turn_speed': 0.7, # angular speed while turning left (rad/s)

src/oomwoo_clean/oomwoo_clean/wall_clean_node.py

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,8 @@
3333
both -> medium (head-on)
3434
3535
All motion values are ROS parameters: the launch seeds them from
36-
`kaia set clean.*`, and they are live -- `ros2 param set /wall_clean arc_omega
37-
0.03` retunes the running robot without a relaunch.
36+
`kaia set clean.*`, and they are live -- `ros2 param set /wall_clean arc_radius
37+
2.0` retunes the running robot without a relaunch.
3838
3939
Interfaces:
4040
subscribes bumper_left/contact ros_gz_interfaces/Contacts
@@ -79,7 +79,7 @@ class WallClean(Node):
7979
def __init__(self) -> None:
8080
super().__init__('wall_clean')
8181
self.v_cruise = self.declare_parameter('v_cruise', 0.15).value
82-
self.arc_omega = self.declare_parameter('arc_omega', 0.1).value
82+
self.arc_radius = self.declare_parameter('arc_radius', 1.5).value
8383
self.v_back = self.declare_parameter('v_back', 0.10).value
8484
self.backoff_s = self.declare_parameter('backoff_s', 0.5).value
8585
self.turn_speed = self.declare_parameter('turn_speed', 0.7).value
@@ -113,7 +113,7 @@ def __init__(self) -> None:
113113
self.state = CRUISE
114114
self.until = self.get_clock().now()
115115
self.create_timer(1.0 / hz, self._tick)
116-
# live tuning: `ros2 param set /wall_clean arc_omega 0.03`
116+
# live tuning: `ros2 param set /wall_clean arc_radius 2.0`
117117
self.add_on_set_parameters_callback(self._on_params)
118118
self.get_logger().info(
119119
'wall_clean: reactive right-wall cleaning -- Ctrl-C to stop')
@@ -123,8 +123,8 @@ def _on_params(self, params) -> SetParametersResult:
123123
for p in params:
124124
if p.name == 'v_cruise':
125125
self.v_cruise = p.value
126-
elif p.name == 'arc_omega':
127-
self.arc_omega = p.value
126+
elif p.name == 'arc_radius':
127+
self.arc_radius = p.value
128128
elif p.name == 'v_back':
129129
self.v_back = p.value
130130
elif p.name == 'backoff_s':
@@ -158,12 +158,18 @@ def _drive(self, lin, ang) -> None:
158158
msg.angular.z = float(ang)
159159
self.pub.publish(msg)
160160

161+
def _cruise_omega(self) -> float:
162+
# Arc rate from the chosen radius: omega = v / r. Driving it from
163+
# arc_radius (rather than a fixed omega) keeps the arc SHAPE constant
164+
# at any cruise speed -- same path whether cruising fast or slow.
165+
return self.v_cruise / max(self.arc_radius, 1e-3)
166+
161167
def _tick(self) -> None:
162168
now = self.get_clock().now()
163169
if self.state == CRUISE:
164170
# forward + gentle RIGHT arc, drifting toward the wall on the right;
165171
# the very first leg (teleop-aimed approach) drives dead straight
166-
arc = 0.0 if self._first_leg else -self.arc_omega
172+
arc = 0.0 if self._first_leg else -self._cruise_omega()
167173
self._drive(self.v_cruise, arc)
168174
left = self._pressed(self._bump_left_t, now)
169175
right = self._pressed(self._bump_right_t, now)
@@ -178,7 +184,7 @@ def _tick(self) -> None:
178184
# Back OUT along the entry arc, reversed. Reversing a differential-
179185
# drive path exactly means negating BOTH linear and angular velocity,
180186
# so at the backoff speed the retrace rate is -entry_arc scaled by
181-
# v_back/v_cruise (same path curvature v_cruise/arc_omega, opposite
187+
# v_back/v_cruise (same path curvature, radius arc_radius, opposite
182188
# travel direction). A straight first leg (entry_arc 0) backs straight.
183189
retrace = -self._entry_arc * self.v_back / max(self.v_cruise, 1e-3)
184190
self._drive(-self.v_back, retrace)

0 commit comments

Comments
 (0)