Skip to content

Commit 48feff0

Browse files
committed
fixed local planner and transform bug
1 parent ea781e3 commit 48feff0

8 files changed

Lines changed: 164 additions & 60 deletions

File tree

dimos/navigation/bt_navigator/navigator.py

Lines changed: 56 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,10 @@
2626
from dimos.core import Module, In, Out, rpc
2727
from dimos.msgs.geometry_msgs import PoseStamped
2828
from dimos.navigation.local_planner.local_planner import BaseLocalPlanner
29+
from dimos.protocol.tf import TF
2930
from dimos.utils.logging_config import setup_logger
3031
from dimos_lcm.std_msgs import Bool
32+
from dimos.utils.transform_utils import apply_transform
3133

3234
logger = setup_logger("dimos.navigation.bt_navigator")
3335

@@ -92,6 +94,9 @@ def __init__(self, publishing_frequency: float = 1.0, **kwargs):
9294
# Local planner reference (to be connected externally)
9395
self.local_planner: Optional[BaseLocalPlanner] = None
9496

97+
# TF listener
98+
self.tf = TF()
99+
95100
logger.info("Navigator initialized")
96101

97102
@rpc
@@ -146,14 +151,19 @@ def set_goal(self, goal: PoseStamped) -> bool:
146151
Returns:
147152
True if goal was accepted, False otherwise
148153
"""
154+
transformed_goal = self._transform_goal_to_odom_frame(goal)
155+
if not transformed_goal:
156+
logger.error("Failed to transform goal to odometry frame")
157+
return False
158+
149159
with self.goal_lock:
150-
self.current_goal = goal
160+
self.current_goal = transformed_goal
151161

152162
with self.state_lock:
153163
self.state = NavigatorState.FOLLOWING_PATH
154164

155165
logger.info(
156-
f"New goal set: ({goal.position.x:.2f}, {goal.position.y:.2f}, {goal.position.z:.2f})"
166+
f"New goal set: ({transformed_goal.position.x:.2f}, {transformed_goal.position.y:.2f}, {transformed_goal.position.z:.2f})"
157167
)
158168
return True
159169

@@ -163,6 +173,11 @@ def connect_local_planner(self, local_planner):
163173
self.local_planner = local_planner
164174
logger.info("Local planner connected")
165175

176+
@rpc
177+
def get_state(self) -> NavigatorState:
178+
"""Get the current state of the navigator."""
179+
return self.state
180+
166181
def _on_odom(self, msg: PoseStamped):
167182
"""Handle incoming odometry messages."""
168183
self.latest_odom = msg
@@ -171,70 +186,94 @@ def _on_goal_request(self, msg: PoseStamped):
171186
"""Handle incoming goal requests."""
172187
self.set_goal(msg)
173188

189+
def _transform_goal_to_odom_frame(self, goal: PoseStamped) -> Optional[PoseStamped]:
190+
"""Transform goal pose to the odometry frame."""
191+
if not self.latest_odom:
192+
logger.warning("No odometry available yet, cannot transform goal")
193+
return None
194+
195+
if not goal.frame_id:
196+
return goal
197+
198+
odom_frame = self.latest_odom.frame_id
199+
if goal.frame_id == odom_frame:
200+
return goal
201+
202+
try:
203+
transform = self.tf.get(
204+
parent_frame=odom_frame,
205+
child_frame=goal.frame_id,
206+
time_point=goal.ts,
207+
time_tolerance=1.0,
208+
)
209+
210+
if not transform:
211+
logger.error(f"Could not find transform from '{goal.frame_id}' to '{odom_frame}'")
212+
return None
213+
214+
pose = apply_transform(goal, transform)
215+
transformed_goal = PoseStamped(
216+
position=pose.position,
217+
orientation=pose.orientation,
218+
frame_id=odom_frame,
219+
ts=goal.ts,
220+
)
221+
return transformed_goal
222+
223+
except Exception as e:
224+
logger.error(f"Failed to transform goal: {e}")
225+
return None
226+
174227
def _control_loop(self):
175228
"""Main control loop running in separate thread."""
176229
while not self.stop_event.is_set():
177230
with self.state_lock:
178231
current_state = self.state
179232

180233
if current_state == NavigatorState.FOLLOWING_PATH:
181-
# Publish goal to global planner
182234
with self.goal_lock:
183235
goal = self.current_goal
184236

185237
if goal is not None:
186238
self.goal.publish(goal)
187239

188-
# Check if goal reached
189240
if self.local_planner and self.is_goal_reached():
190241
logger.info("Goal reached!")
191242
reached_msg = Bool()
192243
reached_msg.data = True
193244
self.goal_reached.publish(reached_msg)
194-
# Reset local planner to clear the path
195245
self.local_planner.reset()
196246
with self.goal_lock:
197247
self.current_goal = None
198248
with self.state_lock:
199249
self.state = NavigatorState.IDLE
200250

201251
elif current_state == NavigatorState.RECOVERY:
202-
# Recovery mode placeholder - transitions back to IDLE
203252
with self.state_lock:
204253
self.state = NavigatorState.IDLE
205254

206-
# Sleep for control period
207255
time.sleep(self.publishing_period)
208256

209257
@rpc
210258
def is_goal_reached(self) -> bool:
211-
"""
212-
Check if the current goal has been reached.
213-
214-
Returns:
215-
True if goal reached, False otherwise
216-
"""
259+
"""Check if the current goal has been reached."""
217260
if self.local_planner is None:
218261
return False
219262

220263
try:
221-
# Call local planner's is_goal_reached RPC
222264
return self.local_planner.is_goal_reached()
223265
except Exception as e:
224266
logger.error(f"Failed to check goal status: {e}")
225267
return False
226268

227269
def stop(self):
228270
"""Stop navigation and return to IDLE state."""
229-
# Cancel any active goal
230271
with self.goal_lock:
231272
self.current_goal = None
232273

233-
# Set state to IDLE
234274
with self.state_lock:
235275
self.state = NavigatorState.IDLE
236276

237-
# Stop the local planner
238277
if self.local_planner:
239278
self.local_planner.reset()
240279

dimos/navigation/global_planner/algo.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,7 @@ def astar(
122122
# Convert world coordinates to grid coordinates directly using vector-like inputs
123123
start_vector = costmap.world_to_grid(start)
124124
goal_vector = costmap.world_to_grid(goal)
125-
logger.info(f"ASTAR {costmap} {start_vector} -> {goal_vector}")
125+
logger.debug(f"ASTAR {costmap} {start_vector} -> {goal_vector}")
126126

127127
# Store original positions for reference
128128
original_start = (int(start_vector.x), int(start_vector.y))
@@ -139,7 +139,7 @@ def astar(
139139
start_in_obstacle = costmap.grid[int(start_vector.y), int(start_vector.x)] >= cost_threshold
140140

141141
if not start_valid or start_in_obstacle:
142-
logger.info("Start position is out of bounds or in an obstacle, finding nearest free cell")
142+
logger.debug("Start position is out of bounds or in an obstacle, finding nearest free cell")
143143
adjusted_start = find_nearest_free_cell(costmap, start, cost_threshold)
144144
# Update start_vector for later use
145145
start_vector = Vector(adjusted_start[0], adjusted_start[1])
@@ -152,7 +152,7 @@ def astar(
152152
goal_in_obstacle = costmap.grid[int(goal_vector.y), int(goal_vector.x)] >= cost_threshold
153153

154154
if not goal_valid or goal_in_obstacle:
155-
logger.info("Goal position is out of bounds or in an obstacle, finding nearest free cell")
155+
logger.debug("Goal position is out of bounds or in an obstacle, finding nearest free cell")
156156
adjusted_goal = find_nearest_free_cell(costmap, goal, cost_threshold)
157157
# Update goal_vector for later use
158158
goal_vector = Vector(adjusted_goal[0], adjusted_goal[1])

dimos/navigation/local_planner/local_planner.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ def _start_planning_thread(self):
107107
self.stop_planning.clear()
108108
self.planning_thread = threading.Thread(target=self._follow_path_loop, daemon=True)
109109
self.planning_thread.start()
110-
logger.info("Started follow path thread")
110+
logger.debug("Started follow path thread")
111111

112112
def _follow_path_loop(self):
113113
"""Main planning loop that runs in a separate thread."""

dimos/protocol/tf/__init__.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,6 @@
1212
# See the License for the specific language governing permissions and
1313
# limitations under the License.
1414

15-
from dimos.protocol.tf.tf import TF, LCMTF, PubSubTF, TFSpec, TFConfig
15+
from dimos.protocol.tf.tf import TF, LCMTF, PubSubTF, TFSpec, TFConfig, TBuffer, MultiTBuffer
1616

17-
__all__ = ["TF", "LCMTF", "PubSubTF", "TFSpec", "TFConfig"]
17+
__all__ = ["TF", "LCMTF", "PubSubTF", "TFSpec", "TFConfig", "TBuffer", "MultiTBuffer"]

dimos/protocol/tf/test_tf.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818

1919
from dimos.core import TF
2020
from dimos.msgs.geometry_msgs import PoseStamped, Quaternion, Transform, Vector3
21-
from dimos.protocol.tf.tf import MultiTBuffer, TBuffer
21+
from dimos.protocol.tf import MultiTBuffer, TBuffer
2222

2323

2424
def test_tf_main():

dimos/robot/unitree_webrtc/type/odometry.py

Lines changed: 3 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -11,22 +11,15 @@
1111
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1212
# See the License for the specific language governing permissions and
1313
# limitations under the License.
14-
import math
15-
from datetime import datetime
16-
from io import BytesIO
17-
from typing import BinaryIO, Literal, TypeAlias, TypedDict
14+
import time
15+
from typing import Literal, TypedDict
1816

1917
from scipy.spatial.transform import Rotation as R
2018

2119
from dimos.msgs.geometry_msgs import PoseStamped, Quaternion, Vector3
2220
from dimos.robot.unitree_webrtc.type.timeseries import (
23-
EpochLike,
2421
Timestamped,
25-
to_datetime,
26-
to_human_readable,
2722
)
28-
from dimos.types.timestamped import to_timestamp
29-
from dimos.types.vector import Vector, VectorLike
3023

3124
raw_odometry_msg_sample = {
3225
"type": "msg",
@@ -104,7 +97,7 @@ def from_msg(cls, msg: RawOdometryMessage) -> "Odometry":
10497
pose["orientation"].get("w"),
10598
)
10699

107-
ts = to_timestamp(msg["data"]["header"]["stamp"])
100+
ts = time.time()
108101
return Odometry(position=pos, orientation=rot, ts=ts, frame_id="world")
109102

110103
def __repr__(self) -> str:

0 commit comments

Comments
 (0)