Skip to content

feat(alfred): whole-robot URDF, alfred-nav (lidar click-and-go with pillar and arms planning via viser), alfred-sim - #4083

Open
KrishnaH96 wants to merge 14 commits into
mainfrom
krishna/feat/alfred
Open

feat(alfred): whole-robot URDF, alfred-nav (lidar click-and-go with pillar and arms planning via viser), alfred-sim#4083
KrishnaH96 wants to merge 14 commits into
mainfrom
krishna/feat/alfred

Conversation

@KrishnaH96

Copy link
Copy Markdown
Contributor

Summary

  • Whole-robot URDF for Alfred (FlowBase, pillar lift, bimanual OpenArm v2.0, Mid-360, D455, D435), built from CAD, shipped as the alfred_description LFS archive
  • alfred-nav: lidar click-and-go on Point-LIO with kronknav, plus pillar and both arms planned from viser on the same coordinator
  • alfred-sim: the same planner on mock hardware in viser, casters animated from cmd_vel
  • Pillar serial driver and alfred-pillar blueprint (Mustafa), moved to the trajectory task
  • Two per-tick manipulation warnings rate-limited to once per 5 s

Validated on the hardware

  • rerun keyboard teleop overrides navigation
  • Click-to-goal in rerun drives to the target
  • Pillar homing and planned lift moves in both directions
  • Viser planner moves on both OpenArms as planned

How to test

Sim, laptop:

    uv sync --extra manipulation --extra alfred
    dimos run alfred-sim            # viser at :8095, WASD in the pygame window

Robot, on the Jetson (arms on can2 left, can3 right):

uv sync --extra misc --extra manipulation --extra alfred
dimos hardware can setup can2 && dimos hardware can setup can3
export DIMOS_POINTLIO_HOST_IP=<this host's IP on the lidar link>
OPENARM_LEFT_CAN=can2 OPENARM_RIGHT_CAN=can3 dimos --rerun-host 0.0.0.0 run alfred-nav \
    --pillarconnection.device-path /dev/serial/by-id/<nano>

Viewer and viser from a laptop:

dimos-viewer --connect rerun+http://<robot>:9877/proxy --ws-url ws://<robot>:3030/ws
ssh -L 8095:127.0.0.1:8095 <robot>          # then http://127.0.0.1:8095

Home the pillar before planning the lift (tools attaching to alfred-nav need its transport):

DIMOS_TRANSPORT=lcm dimos shell
>>> app.PillarConnection.home(); app.PillarConnection.get_status()

AI assistance

Claude Code (Fable 5.1) assisted extensively with implementation, tests and the PR description under my direction. All hardware validation was done by me on the robot.

Checklist

  • I have read and approved the CLA.

Mustafa B and others added 4 commits September 11, 2026 15:50
alfred_description (LFS): alfred_v1.urdf and alfred_v2.urdf (v2 adds the eight caster
steer and drive joints), meshes, the Onshape-to-URDF builder and a frame table. base_link
is the FlowBase odometry origin on the floor. pillar/lift is zero at the top limit switch
with positive up, range -0.500..-0.002 m, as the pillar firmware reports it. mid360_link is
the sensor's bottom face, the frame Point-LIO reports, verified on the robot.

alfred_model.py wraps the URDF with the coordinator joint names and planning groups for
the lift and each arm. alfred-sim runs the planner in viser on mock lift, arms and casters,
with CasterKinematics animating the wheels from cmd_vel. The pillar blueprint moves from
the removed servo task to the canonical trajectory task.
… planned from viser

Point-LIO on the Mid-360 into RayTracingVoxelMap, MLSPlannerNative, DanLocalPlanner,
DanHolonomicTC and MovementManager, the same chain the Go2 runs. AlfredHighLevel stays
the only FlowBase writer; the viewer's keyboard teleop overrides navigation through the
MovementManager mux. AlfredLidarMountTf publishes the URDF's fixed sensor mounts rooted at
mid360_link because Point-LIO owns the lidar's parent edge. The pillar and both OpenArms
sit on a ControlCoordinator and are planned through viser on the alfred_v1 model. The
transport is pinned to LCM because the Point-LIO native does not speak zenoh.

Validated on the robot: click-to-goal in rerun with teleop override, pillar homing and
planned lift moves in both directions, planned arm moves on both arms.
"Skipping incomplete model state" and "TF publish failed" fire on every joint-state tick
while a joint has no reported position, 100 Hz on a robot whose pillar is not yet homed.
Log each at most once per 5 s.
Every streamed target is logged by the trajectory task as a one-point trajectory, so
the idle 30 Hz stream buried the sim log. Publish at 10 Hz while moving and once more
on stop.
@greptile-apps

greptile-apps Bot commented Sep 11, 2026

Copy link
Copy Markdown
Contributor

RetriggerConfidence Score: 1/5

Not safe to merge because four likely functional failures affect manipulation execution, readiness, or status reporting.

Findings

  1. P1 Track physical lift completion
  2. P1 Decouple arms from homing
  3. P1 Separate caster status tracking
  4. P1 Use valid lift home

Summary

  • The focused pillar-homing state check could not run because the supplied runtime is missing zenoh.
  • The focused caster completion check could not run because the supplied runtime is missing dimos_lcm.

Merge safety

Not safe to merge. Physical manipulation completion can get ahead of lift hardware, the Home control is unusable for the lift, arm planning can be blocked by unrelated pillar readiness, and simulation status can remain active indefinitely.

Reviews (1) · Last reviewed commit: "fix(manipulation): throttle per-tick mod..."

Comment thread dimos/robot/diy/alfred/pillar_connection.py Outdated
Comment thread dimos/robot/diy/alfred/blueprints/alfred_nav.py
Comment thread dimos/robot/diy/alfred/blueprints/alfred_sim.py Outdated
Comment thread dimos/robot/diy/alfred/alfred_model.py Outdated
pillar/lift is zero at the top switch, above its reachable range, so a Home preset of
0.0 was rejected by the driver. Home is -0.05, where the firmware parks after homing.
A test checks every home joint against its limits.
@codecov

codecov Bot commented Sep 11, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 40.30374% with 511 lines in your changes missing coverage. Please review.
✅ All tests successful. No failed tests found.

Files with missing lines Patch % Lines
dimos/robot/diy/alfred/pillar_connection.py 17.47% 477 Missing ⚠️
...mos/robot/diy/alfred/blueprints/test_alfred_nav.py 84.15% 16 Missing ⚠️
dimos/robot/diy/alfred/blueprints/alfred_nav.py 83.87% 10 Missing ⚠️
dimos/robot/diy/alfred/alfred_model.py 87.50% 5 Missing ⚠️
dimos/robot/diy/alfred/mount_tf.py 75.00% 2 Missing and 1 partial ⚠️
@@            Coverage Diff             @@
##             main    #4083      +/-   ##
==========================================
- Coverage   78.53%   78.36%   -0.18%     
==========================================
  Files        1457     1467      +10     
  Lines      137798   139087    +1289     
  Branches    11842    12426     +584     
==========================================
+ Hits       108217   108989     +772     
- Misses      26283    26753     +470     
- Partials     3298     3345      +47     
Components Coverage Δ
Tests 94.58% <87.87%> (-0.02%) ⬇️
Flag Coverage Δ
OS-ubuntu-24.04-arm 74.56% <40.30%> (-0.23%) ⬇️
OS-ubuntu-latest 75.26% <40.30%> (-0.13%) ⬇️
Py-3.10 75.04% <40.30%> (-0.35%) ⬇️
Py-3.11 75.24% <40.30%> (-0.15%) ⬇️
Py-3.12 75.25% <40.30%> (-0.14%) ⬇️
Py-3.13 ?
Py-3.14 ?
Py-3.14t ?
SelfHosted-Large 30.80% <32.24%> (+0.01%) ⬆️
SelfHosted-Linux 35.65% <32.24%> (-0.05%) ⬇️
SelfHosted-macOS 34.99% <32.24%> (?)

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
dimos/robot/all_blueprints.py 100.00% <ø> (ø)
dimos/robot/diy/alfred/blueprints/alfred_sim.py 100.00% <100.00%> (ø)
dimos/robot/diy/alfred/blueprints/pillar.py 100.00% <100.00%> (ø)
dimos/robot/diy/alfred/blueprints/test_pillar.py 100.00% <100.00%> (ø)
dimos/robot/diy/alfred/mount_tf.py 85.29% <75.00%> (-2.71%) ⬇️
dimos/robot/diy/alfred/alfred_model.py 87.50% <87.50%> (ø)
dimos/robot/diy/alfred/blueprints/alfred_nav.py 83.87% <83.87%> (ø)
...mos/robot/diy/alfred/blueprints/test_alfred_nav.py 84.15% <84.15%> (ø)
dimos/robot/diy/alfred/pillar_connection.py 17.47% <17.47%> (ø)

... and 90 files with indirect coverage changes

🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

…thout the LFS archive

The registry generator lists every Module subclass, so AlfredLidarMountTf and
CasterKinematics get entries. The home-pose test loads the URDF, which needs the LFS
archive that CI cannot pull; it now skips like the other archive test.
@github-actions github-actions Bot added the ready-to-merge Required CI checks have passed on this PR label Sep 12, 2026
Mustafa B added 2 commits September 12, 2026 17:26
The driver was written for the old Nano firmware and read its English console
output: `pos -50.00 mm (-320 steps)`, `state IDLE|MOVING`, `done. pos`,
`busy - `, and a boot banner string. The new pillar_driver firmware answers on
a machine protocol, so all of that is gone.

- Demultiplex by line shape instead of parsing prose: `<STATUS,ms,code>` events,
  `<ms,joint,metres>` telemetry, and exactly one `ok`/`err` per command line.
  `set echo 0` silences the rest.
- Match replies with a FIFO rather than inferring acks from state lines; the
  firmware guarantees one reply per line, in order.
- Drop the ~200-line coalescing state machine. Motion commands preempt and never
  answer `busy`, so a target goes out straight away; a pacing window keeps the
  newest one instead of queueing it behind the active move.
- Take position from the 50 Hz telemetry stream and stop polling `p`. `get
  status` is never called: ~400 bytes of prose, 40 ms of wire time.
- Re-apply `set echo 0` and `set rate` on every `ready` event. Neither survives
  a board reset.
- Track completion on `move_done`, paired with a deadline, since `limit_hit`,
  `position_lost` and `homing_failed` all end a motion without one.
- `g <mm>`/`x` become joint frames and `stop`; 80 steps/mm becomes 320.

Publish `pillar/lift` from the first telemetry frame, homed or not. The firmware
restores its position from EEPROM on boot, which is good enough to draw the
robot and plan the arms against. Withholding it dropped the joint from the
coordinator's merged state and took both arms down with it. Commanding is still
refused until homed.

PillarConnection, its rpcs, pillar_hardware() and the exported constants are
unchanged, so no call site moved.

Drop test_pillar_connection.py. Every case in it asserted on the old firmware's
prose, so none of it survived the protocol change:

    git show 2ea3a62:dimos/robot/diy/alfred/test_pillar_connection.py
The module existed only to animate eight display joints in viser from cmd_vel.
alfred_v2.urdf already carries those links, so this can be done without a
module and a mock hardware component when someone wants it back:

    git show 2ea3a62:dimos/robot/diy/alfred/caster_kinematics.py

Removing it also takes the casters out of the trajectory task they shared with
the arms and the lift. Their continuous stream kept that task holding an active
motion, so a finished arm or lift plan stayed reported as executing.

`wheels` now selects the urdf only; it no longer changes the joint set.

Also drops the `alfred-lidar-mount-tf` standalone module registration. The
class is still composed into alfred-nav; only the CLI entry goes.
@mustafab0
mustafab0 requested a review from TomCC7 as a code owner September 13, 2026 00:31
@github-actions github-actions Bot removed the ready-to-merge Required CI checks have passed on this PR label Sep 13, 2026
Mustafa B added 4 commits September 12, 2026 17:37
Throttling made a standing fault look like a transient one: a permanently
missing joint and a two-second startup gap both printed one line every five
seconds. That is how the pillar never publishing until homed stayed invisible.

Warn every time instead. A few hundred lines while the adapters come up is
noise that stops on its own; a fault that keeps printing is one you can see.

Reverts 1e477f0.
mount_tf.py goes back to the version on main. Everything this branch appended
to it goes: alfred_mount_transforms(), _descendants(), _is_camera_imager_edge()
and the AlfredLidarMountTf publisher, plus its wiring into alfred-nav and the
test that covered it.

This leaves alfred-nav with no sensor mount tree. Point-LIO publishes
odom -> mid360_link and nothing publishes the edge from there to base_link, so
odom -> base_link does not resolve and MLSPlannerNative and StartRelay cannot
locate the robot. Navigation on hardware is broken until one mount transform
lands to replace the ten this built from the urdf.
alfred-nav needs base_link on tf: MLSPlannerNative, StartRelay and the rerun
viewer all resolve against it, and Point-LIO only supplies odom -> mid360_link.

The version this replaces walked the urdf and published ten edges. alfred-nav
carries no camera or perception module, so nine of them reached nothing. It
also read the geometry through ALFRED_V1_MODEL.load(), which needs the
alfred_description LFS archive, so building the blueprint required an archive
that CI does not always have.

Publish the one edge instead, read off the checked-in alfred.urdf through the
parser that was already there. No LFS, no tree walk, no helpers, and the test
that covers it no longer needs an archive guard.

The number matters: base_link -> mid360_link is
xyz 0.2307 -0.1636 0.437, rpy -0.0096 0.3933 -0.0219. The lidar sits pitched
22.5 degrees. ALFRED.internal_odom_offsets has a hand-written
Pose(0.20, -0.20, 0.30) with identity rotation for the same frame, which is
wrong in both translation and rotation; nothing reads that field.
…sher

alfred-nav needs base_link on tf: MLSPlannerNative, StartRelay and the rerun
viewer all resolve against it, and Point-LIO only supplies odom -> mid360_link.

This branch had been solving that with a second StaticTfPublisher subclass and
a second urdf walk. There is only one thing that differs between the two
blueprint families, and it is which frame odometry already parents:

  alfred-hardware   AlfredHighLevel publishes wheel_odom -> base_link
  alfred-nav        Point-LIO publishes odom -> mid360_link

The mount tree has to root at that frame, or it gets two parents and tf breaks.
So AlfredMountTf grows a root_frame and mount_transforms() inverts the single
edge to it. Everything else, cameras and d455 and the imu included, is
published unchanged and still reaches the root.

root_frame defaults to base_link, so alfred-hardware, alfred-keyboard-teleop
and alfred-mls-nav behave exactly as they do on main.

The other urdf read went through ALFRED_V1_MODEL.load(), which needs the
alfred_description LFS archive; this uses the checked-in alfred.urdf, so the
test covering it no longer needs an archive guard.
…tion

M20Connection, R1ProConnection and B1ConnectionModule are each one Module that
owns its transport directly. The pillar was a plain PillarSerialDriver holding
the serial port, wrapped in a Module that forwarded four rpcs to it.

Fold the driver into PillarConnection and delete the wrapper layer.

Gone with it: the SerialPort and SerialPortFactory Protocols, _open_serial, and
the clock and feedback_callback injection points. Those existed so tests could
pass a fake port and a manual clock, and those tests are gone; pyserial is now
constructed where it is used and time.monotonic is called directly. B1 shows
the convention for a test double anyway, and it is a subclass, not an injected
factory.

PillarSerialDriver, PillarFeedback and PillarStatus had no reference anywhere
outside this file, so they go too; get_status() builds its dict from the state
it already holds.

1086 lines to 863. PillarConnection, its rpcs, pillar_hardware() and the
exported constants are unchanged, so no call site moved.

pyserial stays an alfred extra, so the import stays inside start() for the same
reason AlfredHighLevel defers portal: importing at module scope would make
every blueprint that composes Alfred unimportable without the extra.
@mustafab0 mustafab0 added the backport:skip Skip creating a backport to any release branches label Sep 13, 2026
@github-actions github-actions Bot added the ready-to-merge Required CI checks have passed on this PR label Sep 13, 2026
Comment thread dimos/robot/diy/alfred/mount_tf.py Outdated
Comment thread dimos/manipulation/manipulation_module.py Outdated
Comment thread dimos/robot/diy/alfred/caster_kinematics.py Outdated
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

backport:skip Skip creating a backport to any release branches ready-to-merge Required CI checks have passed on this PR

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants