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
Open
feat(alfred): whole-robot URDF, alfred-nav (lidar click-and-go with pillar and arms planning via viser), alfred-sim#4083KrishnaH96 wants to merge 14 commits into
KrishnaH96 wants to merge 14 commits into
Conversation
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.
KrishnaH96
marked this pull request as ready for review
September 11, 2026 23:17
KrishnaH96
requested review from
Dreamsorcerer,
aclauer,
leshy,
mustafab0,
paul-nechifor and
spomichter
as code owners
September 11, 2026 23:17
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.
Contributor
|
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 Report❌ Patch coverage is @@ 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
Flags with carried forward coverage won't be shown. Click here to find out more.
... and 90 files with indirect coverage changes 🚀 New features to boost your workflow:
|
…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.
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.
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
approved these changes
Sep 13, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
alfred_descriptionLFS archivealfred-nav: lidar click-and-go on Point-LIO with kronknav, plus pillar and both arms planned from viser on the same coordinatoralfred-sim: the same planner on mock hardware in viser, casters animated fromcmd_velalfred-pillarblueprint (Mustafa), moved to the trajectory taskValidated on the hardware
How to test
Sim, laptop:
Robot, on the Jetson (arms on can2 left, can3 right):
Viewer and viser from a laptop:
Home the pillar before planning the lift (tools attaching to alfred-nav need its transport):
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