Skip to content

Team JAMJAM#2293

Open
HaroldGin931 wants to merge 18 commits into
dimensionalOS:danvi/experimental/route-replay-through-SHMfrom
jamjamDimos:jamjam
Open

Team JAMJAM#2293
HaroldGin931 wants to merge 18 commits into
dimensionalOS:danvi/experimental/route-replay-through-SHMfrom
jamjamDimos:jamjam

Conversation

@HaroldGin931
Copy link
Copy Markdown

Summary

Build something you would like to use everyday!

This PR is based on the jamjam branch.

This time, we used DimOS to build a focus helper: the robot dog helps keep your phone away from you during a specific period of time, so you can stay focused.

Video link: Click here
Deck link: Click here
Thanks to our lovely designer.

This was our original idea.

What We Implemented

  • BBox-based selection module, used to activate a specific task module.
  • An interactive web UI for the robot. Thanks to our senior Harry Potter fan.
  • A degraded action strategy: after selecting a bbox, we calculate the spatial coordinate and generate a near point and a far point.

What We Did Not Implement

  • Continuous tracking.

On macOS, DimOS MuJoCo also calls OpenCV. When our custom module needs to call OpenCV too, it can cause a multithreading conflict and crash, so we could not finish continuous tracking in time.

What We Love

We really love the design of Module and Blueprint. They can be freely combined, just like LEGO bricks.

Some people on our team work on BSP and a little bit of circuit design, and they also really liked this design. They said it feels very intuitive.

HaroldGin931 and others added 18 commits May 27, 2026 12:44
…ints/visualization/tests/

- Move flat files into subdirectories:
  - bbox_selection.py → modules/bbox_selection_module.py
  - bbox_distance_follow.py → modules/bbox_distance_behavior_module.py
  - go2_startup_self_check.py → modules/go2_startup_self_check_module.py
  - yoloe_tracking.py → modules/yoloe_tracking_module.py
  - test_bbox_distance_follow.py → tests/test_bbox_selection_module.py
- Add blueprints/ with bbox_distance_follow, go2_startup_self_check, yoloe_tracking_test
- Add visualization/detection2d_overlay.py (detections/selected_bbox/yoloe overlays)
- Update all_blueprints.py with new import paths
- Update README.md with new directory structure and RPC import examples
Compose YOLOE detection + keyboard-controlled Go2 in simulation or on
real hardware.

- Force YOLOE device=cpu in simulation to avoid CoreML/MPS conflict
  with MuJoCo locomotion policy (CoreMLExecutionProvider)
- Route KeyboardTeleop through MovementManager (tele_cmd_vel) to match
  unitree_go2 control path; direct cmd_vel bypass broke MuJoCo sim
- Rerun layout: 2D camera view with cyan YOLOE overlay + 3D view
- Register as 'yoloe-keyboard-teleop' in all_blueprints.py
…h estimation

- BBoxSelectionModule: add click_hit_padding_px and click_snap_distance_px
  so edge/small bbox clicks are more forgiving; nearest-bbox snap fallback
  for near-miss clicks
- BBoxSelectionModule: downgrade per-click diagnostic lines to debug,
  keeping only key state-change lines at info
- TargetLockModule: guard _on_detections publish paths against concurrent
  target-id changes (stale lock prevention)
- BBoxDistanceBehaviorModule: add full task lifecycle logging
  (started / blocked / completed / ended / stopped)
- BBoxDistanceBehaviorModule: suppress repeated 'task started' logs while
  already approaching same target; retain completed target id in done state
  to prevent same-target restart loops
- BBoxDistanceBehaviorModule: adaptive depth estimation bbox expansion
  (depth_bbox_padding_px, depth_bbox_max_padding_px, depth_bbox_padding_step_px)
  to recover from sparse-pointcloud no-depth failures
- websocket_server: downgrade null-field fallback and publish-detail logs to debug
- tests: add regression tests for padding hit, nearest-snap, expanded-depth,
  and websocket null-z handling
- README: document click-hit and depth-estimation tuning knobs with
  recommended adjustment order
…on3DPC world-frame distance

The previous _estimate_bbox_distance() projected raw lidar points directly
using camera intrinsics, treating world-frame XYZ as camera-frame XYZ.
Go2 lidar is already in world frame (frame_id='world'), so the projected
'depth' values were meaningless — using world Z (up) instead of camera Z
(forward), producing distances of 0.082–0.245m regardless of real range.

Method B fix:
- Use Detection3DPC.from_2d() with self.tf to transform world→camera_optical
- Project world-frame lidar points into bbox, get world-frame 3D centroid
- Compute 2D Euclidean distance between robot base_link and detection center
- Keep existing angular P-controller (pixel-based horizontal offset)
- Remove depth percentile / bbox padding config fields (no longer needed)
- Add tf.start() call in start() to activate TF subscription
- Add _safe_track_id() helper for non-numeric detection.id fields

Also includes teleop interrupt (KeyboardTeleop + MovementManager wiring)
added in the previous session: n_workers=12, remappings for nav_cmd_vel /
tele_cmd_vel, stop_movement → teleop_active interrupt.
- Switch yoloe_target_lock_distance_follow blueprint base from
  unitree_go2_basic to unitree_go2, adding VoxelGridMapper, CostMapper,
  and ReplanningAStarPlanner for SLAM map rendering and click-to-navigate
- Remove duplicate MovementManager.blueprint() (already in unitree_go2)
- Remove world/lidar visible=False override so SLAM map appears in 3D view
- BBoxDistanceBehaviorModule: add optional _planner: ReplanningAStarPlannerSpec
  injected by blueprint; call cancel_goal() when a new bbox task starts
- MovementManager._on_click: publish stop_movement before forwarding goal,
  so any active bbox tracking task is cancelled when user clicks SLAM map
- n_workers: 12 -> 16 to accommodate added navigation modules
What lands on jamjam_ui:

1. dimos/apps/marauders_map/ — new namespace, full Harry-Potter "Wrath of
   Filch" web app on :7782. Composes upstream nav + jamjam's YOLO-E
   target-lock/follow stack into one autoconnect() blueprint. Parchment
   UI, intro overlay with magick-in char-by-char reveal, click-to-track
   from map + roster + gallery, click-to-navigate, on-page WASD teleop,
   quest chip, looped BGM.

2. dimos/robot/unitree/ hardware fixes:
   - connection.py: free_walk() now also sends SwitchJoystick(1027,
     data=True) — fixes the "dog sways in place" symptom where lx/ly
     were interpreted as body lean instead of velocity.
   - go2/connection.py: 2 FreeWalk publishes on init with a settle
     gap + 15s background heartbeat. Guarded by
     hasattr(connection,"free_walk") so MuJoCo/DimSim/Replay don't crash.

3. scripts/run-blueprint.sh — auto-logged runner. Mode resolution by env
   (SIM > REPLAY > ROBOT_IP). Uses mjpython on macOS for MuJoCo's
   main-thread GL context. Writes /tmp/dimos-logs/<bp>-<ts>.log with
   git HEAD + uncommitted file count header. Auto-rotates to newest 20.

4. examples/go2_phone_control/ — phone-as-controller webapp (mock + real),
   plus minimal direct-WebRTC scripts (go2_walk_*.py, go2_person_aware_walk.py)
   and a standalone halt.py for E-STOP.

5. HACKATHON.md — branch-level README with the structure, run modes,
   data flow diagram, and E-STOP layers.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Add four Hogwarts house badges (Gryffindor / Hufflepuff / Ravenclaw /
  Slytherin) and serve them via /icons/{house}.png from ReidMapModule.
* Assign every HP character in hp_characters.js a canonical `house`
  field. Teachers, non-Hogwarts wizards, creatures, and Muggles get
  null = no badge (per design rule).
* Render the badge in three places in marauders_map.html: roster row
  inline, gallery card top-right, hover tooltip dedicated row.
* Clicking a person in the right-side "Those Present" list now opens
  the same HP-flavoured confirm parchment that a footprint click on
  the map opens, instead of selecting silently. Tracked-person
  re-click still releases without re-prompting.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented May 28, 2026

Greptile Summary

This PR adds Team JAMJAM's hackathon focus-helper application: a Harry-Potter-themed "Marauder's Map" web UI that plots detected people on an occupancy-grid floor plan and lets the robot dog follow a selected person (to keep a phone out of reach). The stack builds on a new BBoxSelectionModule / TargetLockModule / BBoxDistanceBehaviorModule pipeline plus a Socket.IO web server, all wired together via an autoconnect blueprint.

  • New ReidMapModule (835 lines) serves a Socket.IO web page showing a live occupancy-grid map with reID-tracked person markers; web clicks synthesize PointStamped events that flow into the existing BBoxSelectionModule without touching upstream modules.
  • UnitreeWebRTCConnection gains a configurable connection method, a two-step FreeWalk + SwitchJoystick gait init (plus 15-second heartbeat), and a "look up on stop" head-tilt behaviour.
  • MovementManager now filters out color-image-frame clicks before they reach the navigation goal path, and publishes stop_movement before issuing a new A* goal.

Confidence Score: 3/5

The web-UI stop signal won't reach the selection/lock modules due to a missing topic remapping, and the connect-event handler iterates a dict that the detection thread can concurrently mutate — both need fixes before the Marauder's Map blueprint is reliable on real hardware.

Two issues need attention before this runs cleanly in the field. The ReidMapModule.stop_movement output is never remapped to teleop_active, so web-driven deselect is silently dropped when the detection stream has no current frame — the robot can restart following unexpectedly. Separately, _people_payload() is called from the Socket.IO asyncio thread while the detection subscription thread can add or remove entries from self._people, creating a real risk of RuntimeError: dictionary changed size during iteration whenever a new browser tab connects during active detection. The rest of the stack — the new bbox-selection/follow pipeline, FreeWalk heartbeat, movement-manager click filter, and macOS SDL fix — is well-structured and low-risk.

dimos/apps/marauders_map/module.py (thread-safety and CORS) and dimos/apps/marauders_map/blueprint.py (missing stop_movementteleop_active remapping for ReidMapModule).

Security Review

  • Open CORS policy (cors_allowed_origins="*") on the Socket.IO server in dimos/apps/marauders_map/module.py allows any web origin on the same network to connect and send teleop, selection, and navigation commands to the robot.

Important Files Changed

Filename Overview
dimos/apps/marauders_map/module.py New 835-line ReidMapModule: Socket.IO web server serving the Marauder's Map UI; has a thread-safety bug where the connect handler iterates _people from the asyncio thread while the detection stream thread can concurrently modify it, and CORS is fully open.
dimos/apps/marauders_map/blueprint.py New blueprint wiring the Marauder's Map stack; ReidMapModule.stop_movement output is not remapped to the teleop_active topic, so web-UI stop signals won't reach BBoxSelectionModule or TargetLockModule.
dimos/robot/unitree/connection.py Adds configurable WebRTC connection method, head look-up on stop, and two-step FreeWalk+SwitchJoystick; _cancel_stop_timer now calls look_up() which can fire spuriously during mid-movement timer cancellation.
dimos/robot/unitree/go2/connection.py Adds FreeWalk initialization (double-publish + SwitchJoystick) and a daemon heartbeat thread that re-asserts FreeWalk every 15 s; heartbeat lacks a clean shutdown signal.
dimos/robot/custom/modules/bbox_selection_module.py New BBoxSelectionModule: click-to-bbox selection with RLock protection, stop_movement and clear_selection_request inputs; logic is sound and well-tested.
dimos/robot/custom/tasks/bbox_distance_behavior_module.py New BBoxDistanceBehaviorModule: proportional distance-follow control loop; implementation is straightforward with proper RLock usage and stop-event for the background command thread.
dimos/navigation/movement_manager/movement_manager.py Adds _is_navigation_click filter to ignore color_image-frame clicks in the movement manager, and publishes stop_movement before handing a new goal to the planner; logic is correct.
dimos/visualization/rerun/websocket_server.py Refactors click/twist field extraction to use a _coerce_float helper with null-safe fallback and adds verbose click logging; straightforward improvement with no issues.
dimos/robot/unitree/keyboard_teleop.py Fixes macOS crash by using setdefault("SDL_VIDEODRIVER", "cocoa") on darwin instead of unconditionally forcing x11; correct and safe.
scripts/run-blueprint.sh New helper script that runs any blueprint and tee-logs output to /tmp/dimos-logs/ with auto-rotation; handles SIM/REPLAY/ROBOT_IP modes and the mjpython dispatch for MuJoCo on macOS.

Sequence Diagram

sequenceDiagram
    participant Browser as Browser (port 7782)
    participant ReidMap as ReidMapModule
    participant BBoxSel as BBoxSelectionModule
    participant TgtLock as TargetLockModule
    participant BBoxDist as BBoxDistanceBehaviorModule
    participant MovMgr as MovementManager
    participant Robot as Go2 Robot

    Browser->>ReidMap: "Socket.IO select {id}"
    ReidMap->>BBoxSel: stop_movement Out[Bool] NOT CONNECTED missing remap
    ReidMap->>BBoxSel: clicked_point PointStamped bbox center
    BBoxSel->>TgtLock: selected_bbox Detection2DArray
    TgtLock->>BBoxDist: locked_bbox Detection2DArray
    BBoxDist->>MovMgr: nav_cmd_vel Twist
    MovMgr->>Robot: cmd_vel

    Browser->>ReidMap: "Socket.IO teleop {linear_x, angular_z}"
    ReidMap->>MovMgr: tele_cmd_vel Twist
    MovMgr->>BBoxSel: teleop_active Bool clears selection
    MovMgr->>Robot: cmd_vel priority teleop

    Browser->>ReidMap: "Socket.IO navigate {wx, wy}"
    ReidMap->>MovMgr: goal_request PoseStamped
    MovMgr->>Robot: "nav_cmd_vel via A*"
Loading

Reviews (1): Last reviewed commit: "demo blueprint yoloe-spatial-standoff-fo..." | Re-trigger Greptile

Comment on lines +329 to +335
@self.sio.event # type: ignore[untyped-decorator]
async def connect(sid, environ) -> None: # type: ignore[no-untyped-def]
logger.info(f"Marauder's Map client connected: {sid}")
# Send the cached map + current people so a fresh client is not blank.
if self._map_meta is not None:
await self.sio.emit("map", self._map_meta, room=sid) # type: ignore[union-attr]
await self.sio.emit("people", {"people": self._people_payload()}, room=sid) # type: ignore[union-attr]
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 Thread safety: _people iterated from asyncio thread while stream thread mutates it

connect calls self._people_payload() from the Socket.IO / asyncio event loop, which iterates self._people (line 741 for tid, e in self._people.items()). Meanwhile _on_detections — running on the RxPY stream subscription thread — can write to self._people (via _write_entry and _expire_people) at any time. If the stream thread adds or removes a key while the asyncio thread is mid-iteration Python raises RuntimeError: dictionary changed size during iteration, crashing the connect handler silently. Unlike _latest_image / _latest_pc, _people and _stable_to_raw_det are never guarded by self._lock when read from the asyncio thread.

# Server
# ------------------------------------------------------------------ #
def _create_server(self) -> None:
self.sio = socketio.AsyncServer(async_mode="asgi", cors_allowed_origins="*")
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 security cors_allowed_origins="*" makes the Socket.IO endpoint reachable from any web origin. On a shared or conference Wi-Fi network (common at hackathons and field demos) a page on any other device can drive the robot's selection, teleop, and navigation streams. Restricting to localhost or the field subnet provides a simple first layer of defence.

Suggested change
self.sio = socketio.AsyncServer(async_mode="asgi", cors_allowed_origins="*")
self.sio = socketio.AsyncServer(async_mode="asgi", cors_allowed_origins="*") # TODO: restrict to trusted origin(s) before production use

Comment on lines +175 to +200
.remappings(
[
# ─── Selection chain ────────────────────────────────────────────
# Both Rerun camera click and our web "click" feed BBoxSelectionModule
# through its existing `clicked_point` input. BBoxSelectionModule is
# the sole writer of /user_selected_bbox; TargetLockModule consumes
# it; the locked output is remapped to /selected_bbox for the
# follower task and the Rerun green-box overlay.
(BBoxSelectionModule, "selected_bbox", "user_selected_bbox"),
(TargetLockModule, "selected_bbox", "user_selected_bbox"),
(TargetLockModule, "locked_bbox", "selected_bbox"),
# ─── Follow task → cmd_vel mux ──────────────────────────────────
(BBoxDistanceBehaviorModule, "cmd_vel", "nav_cmd_vel"),
# ─── Web teleop → mux (priority) ────────────────────────────────
(ReidMapModule, "cmd_vel", "tele_cmd_vel"),
# ─── Teleop activity → pause follow + clear selection ───────────
# jamjam e9a82939: BBoxSelectionModule and TargetLockModule both gained
# `stop_movement: In[Bool]` so any source of teleop-activity wipes the
# current target and the old bbox can't auto-restart the follow on the
# next detection frame.
(MovementManager, "stop_movement", "teleop_active"),
(BBoxSelectionModule, "stop_movement", "teleop_active"),
(TargetLockModule, "stop_movement", "teleop_active"),
# ─── Marauder's Map world localization ──────────────────────────
(ReidMapModule, "pointcloud", "global_map"),
]
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 ReidMapModule.stop_movement output is never remapped to teleop_active

The blueprint renames BBoxSelectionModule.stop_movement, TargetLockModule.stop_movement, and MovementManager.stop_movement all to the topic teleop_active, but ReidMapModule.stop_movement: Out[Bool] is left on the default stop_movement topic. These two topics won't be connected by autoconnect, so the stop_movement.publish(Bool(data=True)) calls inside the select, deselect, and navigate Socket.IO handlers never reach BBoxSelectionModule or TargetLockModule. When the tracker has no live detections the backup clear-via-click path also short-circuits, leaving a stale selection active that can restart following the moment a new detection frame arrives.

Comment on lines +347 to +362
def _freewalk_heartbeat(self) -> None:
"""Re-assert FreeWalk locomotion every 15 s as a soft watchdog.

On real hardware we sometimes see the dog silently fall back to
BalanceStand (joystick lx/ly -> body lean, no walking). Re-issuing
FreeWalk is idempotent when already in that mode, so this is a
cheap insurance policy that fixes the most common "dog only sways"
report without forcing the user to restart the blueprint.
"""
while True:
time.sleep(15.0)
try:
self.connection.free_walk()
except Exception:
# Connection may be torn down during shutdown; ignore.
pass
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 _freewalk_heartbeat loops forever with no shutdown path

The heartbeat thread runs while True and is only a daemon=True thread — it lives for the entire process lifetime and cannot be interrupted during a clean stop() sequence. If teardown blocks on the connection or a command is dispatched mid-shutdown, the exception is silently swallowed and the loop continues. A threading.Event stop signal (checked after each time.sleep(15)) would let GO2Connection.stop() interrupt the loop quickly.

Comment on lines 515 to 525
if self.stop_timer:
self.stop_timer.cancel()
self.stop_timer = None
# Reaching the timeout means commands stopped arriving while moving:
# treat that as a stop and raise the head once.
if self.look_up_on_stop and self._was_moving:
self._was_moving = False
self.look_up()

def disconnect(self) -> None:
"""Disconnect from the robot and clean up resources."""
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 look_up() fires spuriously when _cancel_stop_timer is used to cancel (not fire) a pending timer

If _cancel_stop_timer is called while a new movement command is being prepared (to cancel the previous stop timer), self._was_moving is still True from the prior motion, so look_up() is called — tilting the head right before the robot resumes walking. _update_head_posture would then immediately call reset_posture(), causing a brief involuntary head-tilt on every command boundary. Limiting the look_up branch to the actual timeout path (e.g., by using a dedicated timer-callback method rather than the shared cancel helper) would avoid this.

@leshy leshy added the hackaton label May 29, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants