Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions data/.lfs/go2_slamabuse3.db.tar.gz
Git LFS file not shown
3 changes: 1 addition & 2 deletions dimos/memory2/module.py
Original file line number Diff line number Diff line change
Expand Up @@ -314,9 +314,8 @@ def on_msg(msg: Any) -> None:
"[%s] No tf available for frame '%s' at time %s (msg ts: %s), storing without pose",
name,
frame_id,
ts,
getattr(msg, "ts", None),
)
stream.append(msg, ts=ts, pose=pose)
stream.append(msg, ts=time.time(), pose=pose)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P1 Recording timestamp no longer tracks message time

ts was previously computed as getattr(msg, "ts", None) or time.time() and passed through to both the tf lookup and stream.append. It is still used for the tf lookup on line 309, but stream.append now always receives time.time(). Any message that carries its own hardware or protocol timestamp will be stored in the stream under the wall-clock arrival time instead of the message's own ts. For replayed or sensor-timestamped data this will cause the recording index to disagree with the pose timestamps that were resolved against the message's original ts.


self.register_disposable(Disposable(input_topic.subscribe(on_msg)))
15 changes: 12 additions & 3 deletions dimos/protocol/tf/tf.py
Original file line number Diff line number Diff line change
Expand Up @@ -154,12 +154,21 @@ def get_transform(

return None

def get(self, *args, **kwargs) -> Transform | None: # type: ignore[no-untyped-def]
simple = self.get_transform(*args, **kwargs)
def get(
self,
parent_frame: str,
child_frame: str,
time_point: float | None = None,
time_tolerance: float | None = None,
) -> Transform | None:
if parent_frame == child_frame:
return Transform(frame_id=parent_frame, child_frame_id=child_frame)

simple = self.get_transform(parent_frame, child_frame, time_point, time_tolerance)
if simple is not None:
return simple

complex = self.get_transform_search(*args, **kwargs)
complex = self.get_transform_search(parent_frame, child_frame, time_point, time_tolerance)

if complex is None:
return None
Expand Down
8 changes: 4 additions & 4 deletions dimos/robot/cli/topic.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,11 +37,11 @@
def _resolve_type(type_name: str) -> type:
for module_name in _modules_to_try:
try:
module = importlib.import_module(module_name)
if hasattr(module, type_name):
return getattr(module, type_name) # type: ignore[no-any-return]
module = importlib.import_module(f"{module_name}.{type_name}")
except ImportError:
continue
if hasattr(module, type_name):
return getattr(module, type_name) # type: ignore[no-any-return]
Comment on lines 37 to +44
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 _resolve_type no longer handles types that are exported from a package __init__

The old code imported the package (e.g. dimos.msgs.geometry_msgs) and checked whether the class was an attribute of that module — which works for packages that re-export their classes in __init__.py. The new code tries to import a sub-module named after the type (e.g. dimos.msgs.geometry_msgs.Twist). If that sub-module doesn't exist but the class is still accessible via the package init, ImportError is caught, the loop continues, and all entries are exhausted, ending in a ValueError. Any call-site that previously worked through the package-level export will silently break. Were the dimos.msgs.* packages recently reorganised so every message class now lives in its own sub-module? If the package inits still re-export classes, the old approach would still work and the new one may miss some types.


raise ValueError(f"Could not find type '{type_name}' in any known message modules")

Expand Down Expand Up @@ -81,7 +81,7 @@ def _on_message(msg: object) -> None:
def on_msg(channel: str, data: bytes) -> None:
_, msg_name = channel.split("#", 1) # e.g. "nav_msgs.Odometry"
pkg, cls_name = msg_name.split(".", 1) # "nav_msgs", "Odometry"
module = importlib.import_module(f"dimos.msgs.{pkg}")
module = importlib.import_module(f"dimos.msgs.{pkg}.{cls_name}")
cls = getattr(module, cls_name)
print(cls.lcm_decode(data))

Expand Down
13 changes: 13 additions & 0 deletions dimos/robot/unitree/connection.py
Original file line number Diff line number Diff line change
Expand Up @@ -303,6 +303,19 @@ def set_obstacle_avoidance(self, enabled: bool = True) -> None:
{"api_id": 1001, "parameter": {"enable": int(enabled)}},
)

def set_lidar(self, enabled: bool) -> None:
"""Turn the L1 lidar on or off. The Go2 boots with lidar on; turning it
off saves power and stops the spinning sound."""

def publish() -> None:
# rt/utlidar/switch takes a std_msgs::String of "ON" / "OFF"
self.conn.datachannel.pub_sub.publish_without_callback(
RTC_TOPIC["ULIDAR_SWITCH"],
data="ON" if enabled else "OFF",
)

self.loop.call_soon_threadsafe(publish)

def free_walk(self) -> bool:
"""Activate FreeWalk locomotion mode — enables walking and velocity commands."""
return bool(self.publish_request(RTC_TOPIC["SPORT_MOD"], {"api_id": SPORT_CMD["FreeWalk"]}))
Expand Down
3 changes: 3 additions & 0 deletions dimos/robot/unitree/dimsim_connection.py
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,9 @@ def balance_stand(self) -> bool:
def set_obstacle_avoidance(self, enabled: bool = True) -> None:
pass

def set_lidar(self, enabled: bool) -> None:
pass

def enable_rage_mode(self) -> bool:
return True

Expand Down
14 changes: 8 additions & 6 deletions dimos/robot/unitree/go2/cli/go2tool.py
Original file line number Diff line number Diff line change
Expand Up @@ -162,12 +162,14 @@ def _on_device(d: Go2Device) -> None:
typer.echo("Found:")
for i, d in enumerate(devices, 1):
typer.echo(f" {i}. {d.name} ({d.address})")
default = "1" if len(devices) == 1 else None
idx = typer.prompt("Select device", default=default, type=int)
if not 1 <= idx <= len(devices):
typer.echo("Invalid selection.", err=True)
raise typer.Exit(1)
target = devices[idx - 1].address
if len(devices) == 1:
target = devices[0].address
else:
idx = typer.prompt("Select device", type=int)
if not 1 <= idx <= len(devices):
typer.echo("Invalid selection.", err=True)
raise typer.Exit(1)
target = devices[idx - 1].address

wifi_ssid = ssid if ssid is not None else typer.prompt("Wi-Fi SSID")
wifi_password = (
Expand Down
15 changes: 15 additions & 0 deletions dimos/robot/unitree/go2/connection.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ class Go2Mode(str, Enum):
class ConnectionConfig(ModuleConfig):
ip: str = Field(default_factory=lambda m: m["g"].robot_ip)
mode: Go2Mode = Go2Mode.DEFAULT
lidar_auto_off: bool = False


class Go2ConnectionProtocol(Protocol):
Expand All @@ -79,6 +80,7 @@ def standup(self) -> bool: ...
def liedown(self) -> bool: ...
def balance_stand(self) -> bool: ...
def set_obstacle_avoidance(self, enabled: bool = True) -> None: ...
def set_lidar(self, enabled: bool) -> None: ...
def enable_rage_mode(self) -> bool: ...
def publish_request(self, topic: str, data: dict) -> dict: ... # type: ignore[type-arg]

Expand Down Expand Up @@ -166,6 +168,9 @@ def balance_stand(self) -> bool:
def set_obstacle_avoidance(self, enabled: bool = True) -> None:
pass

def set_lidar(self, enabled: bool) -> None:
pass

def enable_rage_mode(self) -> bool:
return True

Expand Down Expand Up @@ -268,10 +273,15 @@ def onimage(image: Image) -> None:

self.connection.set_obstacle_avoidance(self.config.g.obstacle_avoidance)

self.connection.set_lidar(True)

# self.record("go2_bigoffice")

@rpc
def stop(self) -> None:
if self.config.lidar_auto_off:
self.connection.set_lidar(False)

self.liedown()

if self.connection:
Expand Down Expand Up @@ -337,6 +347,11 @@ def balance_stand(self) -> bool:
"""Enter BalanceStand: neutral state for switching locomotion modes"""
return self.connection.balance_stand()

@rpc
def set_lidar(self, enabled: bool) -> None:
"""Turn the L1 lidar on or off."""
self.connection.set_lidar(enabled)

@rpc
def enable_rage_mode(self) -> bool:
"""Enable Rage Mode (~2.5 m/s forward velocity envelope).
Expand Down
3 changes: 3 additions & 0 deletions dimos/robot/unitree/mujoco_connection.py
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,9 @@ def balance_stand(self) -> bool:
def set_obstacle_avoidance(self, enabled: bool = True) -> None:
pass

def set_lidar(self, enabled: bool) -> None:
pass

def enable_rage_mode(self) -> bool:
return True

Expand Down
Loading