Skip to content

feat(recording): add CDR MCAP store and native recording - #4732

Open
TomCC7 wants to merge 17 commits into
cc/feat/cdr-unit-transportsfrom
cc/feat/cdr-unit-recording
Open

TomCC7 wants to merge 17 commits into
cc/feat/cdr-unit-transportsfrom
cc/feat/cdr-unit-recording

Conversation

@TomCC7

@TomCC7 TomCC7 commented Oct 20, 2026 •

Copy link
Copy Markdown
Member

What this adds

  • Record generated messages from LCM or Zenoh into SQLite or MCAP with Rust, without decoding the payload.
  • Embed complete ros2msg schemas in MCAP and mark channels as cdr.
  • Read and replay CDR MCAP files through the separate CdrMcapStore.

Base: #4774 (shared native SDK), after #4722. Recording uses reception time; the original header.stamp stays in the payload.

How to test

From this PR's checkout, with Linux, uv, Rust and the native build dependencies:

uv venv .venv-cdr-recording --python 3.12
DIMOS_ALLOW_MISSING_COCKPIT=1 uv pip install --python .venv-cdr-recording/bin/python -e . -e examples/cdr-recording
source .venv-cdr-recording/bin/activate
cargo build --locked --manifest-path dimos/experimental/memory/rust/Cargo.toml --target-dir build/cdr-recorder -j2

dimos --transport zenoh --viewer none --n-workers 1 \
  --record mcap --record-engine rust --record-topics pose \
  --record-executable "$PWD/build/cdr-recorder/debug/dimos-memory-recorder" \
  run dimos-cdr-recording-example.pose

Wait for recorder readiness, record for about 15 seconds, then press Ctrl+C once. Open the saved artifact_path in Foxglove and view pose in frame map. It should move in a circle. No robot, ROS installation or API key is needed.

Prerequisites, expected output and Python readback.

WIP: local installation, recording and Python readback were checked. Foxglove UI rendering and Nix were not verified. Business blueprint migration, compression and default cutover remain later work.

Shared CDR foundation

Standard ROS definitions are fetched and verified through the existing source lock and generation pipeline in #4366. Native SDK support is included in #4722. Shared Python value implementations and regressions are reviewed in #4800–#4803 before the #4734 caller migration. SDK and consumers use the same generated Rust message crate, including Time, TransformStamped and TFMessage; there is no separate TF schema package or generation command. Camera/depth callers migrate in #4734 and mapping/navigation callers in #4737.

The external CDR and transforms library pins remain unchanged. Local verification passed 122 SDK tests, all existing native consumer suites, and all 24 real native/DimSim/port acceptance tests without skips. Seven staged Nix source layouts resolve exactly one message crate with locked Cargo metadata. This is not a full Nix build or hardware validation. Fresh CI runs verify #4722 independently and the combined stack tip.

Stack #4804 contains the 12 original feature PRs and four separately reviewed adapter/buffer PRs (#4800–#4803). The redundant #4774 is superseded by #4722. No feature PR has been merged into main; existing conflicts with main and the WIP cutover failures in #4739 remain.

@github-actions github-actions Bot left a comment •

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.

go2 replay realtime (arm64)

Details
Benchmark suite Current: 12c81e0 Previous: ecc2b8f Ratio
peak memory 1588.184 MB 1570.996 MB 1.01
peak threads 347 threads 355 threads 0.98
network (transport) 2724.766 MB 2730.387 MB 1.00
disk write 1.742 MB 1.496 MB 1.16
instructions 115.685 G 115 G 1.01

This comment was automatically generated by workflow using github-action-benchmark.

@codecov

codecov Bot commented Oct 20, 2026 •

Copy link
Copy Markdown

❌ 1 Tests Failed:

Tests completed Failed Passed Skipped
7312 1 7311 70
View the top 1 failed test(s) by shortest run time
dimos.perception.detection.type.test_detection3d::test_guess_projection
Stack Traces | 0.286s run time
get_moment_2d = <functools._lru_cache_wrapper object at 0x3bf4c2980>
publish_moment = <function publish_moment.<locals>.publisher at 0x3aff42de0>

    def test_guess_projection(get_moment_2d, publish_moment) -> None:
        moment = get_moment_2d()
        for key, value in moment.items():
            print(key, "====================================")
            print(value)
    
        moment.get("camera_info")
        detection2d = moment.get("detections2d")[0]
        tf = moment.get("tf")
        tf.get("camera_optical", "world", detection2d.ts, 5.0)
    
        # for stash
        # detection3d = Detection3D.from_2d(detection2d, 1.5, camera_info, transform)
        # print(detection3d)
    
>       publish_moment(moment)

detection2d = Detection2DBBox(bbox=(503.437255859375, 249.89385986328125, 655.950439453125, 469.82879638671875), track_id=1, class_i...70.490248, image=Image(shape=(720, 1280, 3), format=BGR, dtype=uint8, frame_id='camera_optical', ts=1757960670.490248))
get_moment_2d = <functools._lru_cache_wrapper object at 0x3bf4c2980>
key        = 'detections2d'
moment     = {'camera_info': CameraInfo(height=720, width=1280, distortion_model='equidistant', frame_id='camera_optical', ts=17926...optical', ts=1757960670.490248), 'lidar_frame': PointCloud(points=31284, frame_id='world', ts=1757960670.4755309), ...}
publish_moment = <function publish_moment.<locals>.publisher at 0x3aff42de0>
tf         = <dimos.protocol.tf.tf.TF object at 0x4153e93a0>
value      = <dimos.perception.detection.type.detection2d.imageDetections2D.ImageDetections2D object at 0x41510c6e0>

.../detection/type/test_detection3d.py:37: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
.../perception/detection/conftest.py:126: in publisher
    detections.publish(detections2d_val.to_ros_detection2d_array())
        detections = <dimos.core.transport.LCMTransport object at 0x4154c2ea0>
        detections2d_val = <dimos.perception.detection.type.detection2d.imageDetections2D.ImageDetections2D object at 0x41510c6e0>
        moment     = {'camera_info': CameraInfo(height=720, width=1280, distortion_model='equidistant', frame_id='camera_optical', ts=17926...optical', ts=1757960670.490248), 'lidar_frame': PointCloud(points=31284, frame_id='world', ts=1757960670.4755309), ...}
dimos/core/stream.py:107: in publish
    self.broadcast(None, msg)
        msg        = <dimos_lcm.vision_msgs.Detection2DArray.Detection2DArray object at 0x3afeffdc0>
        self       = <dimos.core.transport.LCMTransport object at 0x4154c2ea0>
dimos/core/transport.py:164: in broadcast
    self.lcm.publish(self.topic, msg)
        _          = None
        msg        = <dimos_lcm.vision_msgs.Detection2DArray.Detection2DArray object at 0x3afeffdc0>
        self       = <dimos.core.transport.LCMTransport object at 0x4154c2ea0>
.../protocol/pubsub/encoders.py:68: in publish
    encoded_message = self.encode(message, topic)
        __class__  = <class 'dimos.protocol.pubsub.encoders.PubSubEncoderMixin'>
        message    = <dimos_lcm.vision_msgs.Detection2DArray.Detection2DArray object at 0x3afeffdc0>
        self       = <dimos.protocol.pubsub.impl.lcmpubsub.LCM object at 0x414e6f050>
        topic      = Topic(topic='/detections', lcm_type=<class 'dimos.msgs.vision_msgs.Detection2DArray.Detection2DArray'>, message_type=<class 'dimos.msgs.vision_msgs.Detection2DArray.Detection2DArray'>)
.../protocol/pubsub/encoders.py:149: in encode
    return descriptor.codec.encode(msg)
        descriptor = MessageDescriptor(message_type=<class 'dimos.msgs.vision_msgs.Detection2DArray.Detection2DArray'>, encoding='lcm', nam...sgs.Detection2DArray', codec=LCMCodec(message_type=<class 'dimos.msgs.vision_msgs.Detection2DArray.Detection2DArray'>))
        msg        = <dimos_lcm.vision_msgs.Detection2DArray.Detection2DArray object at 0x3afeffdc0>
        self       = <dimos.protocol.pubsub.impl.lcmpubsub.LCM object at 0x414e6f050>
        topic      = Topic(topic='/detections', lcm_type=<class 'dimos.msgs.vision_msgs.Detection2DArray.Detection2DArray'>, message_type=<class 'dimos.msgs.vision_msgs.Detection2DArray.Detection2DArray'>)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = LCMCodec(message_type=<class 'dimos.msgs.vision_msgs.Detection2DArray.Detection2DArray'>)
message = <dimos_lcm.vision_msgs.Detection2DArray.Detection2DArray object at 0x3afeffdc0>

    def encode(self, message: T) -> bytes:
        if not isinstance(message, self.message_type):
>           raise TypeError("Message does not match the declared type")
E           TypeError: Message does not match the declared type

message    = <dimos_lcm.vision_msgs.Detection2DArray.Detection2DArray object at 0x3afeffdc0>
self       = LCMCodec(message_type=<class 'dimos.msgs.vision_msgs.Detection2DArray.Detection2DArray'>)

.../protocol/pubsub/codec.py:41: TypeError

To view more test analytics, go to the Test Analytics Dashboard
📋 Got 3 mins? Take this short survey to help us improve Test Analytics.

@TomCC7
TomCC7 added this pull request to stack #4743 October 20, 2026 22:26
@TomCC7
TomCC7 force-pushed the cc/feat/cdr-unit-recording branch from 59b492b to 3ce7f4e Compare October 21, 2026 02:08
@TomCC7 TomCC7 changed the title feat(recording): consolidate CDR capture replay and collection feat(recording): add CDR storage and replay on shared codecs Oct 21, 2026
@TomCC7 TomCC7 changed the title feat(recording): add CDR storage and replay on shared codecs feat(recording): add CDR MCAP store and native recording Oct 21, 2026

@TomCC7 TomCC7 left a comment

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

need to think about an example used in this pr

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

verify that we do need this and can't be solved by rust's own cdr lib

Comment thread dimos/memory/store/cdr_mcap.py
@TomCC7
TomCC7 removed this pull request from stack #4743 October 22, 2026 18:51
@TomCC7
TomCC7 changed the base branch from cc/feat/cdr-unit-transports to cc/fix/cdr-native-sdk October 22, 2026 18:52
@TomCC7
TomCC7 added this pull request to stack #4776 October 22, 2026 18:52
@TomCC7
TomCC7 removed this pull request from stack #4776 October 23, 2026 08:47
@TomCC7
TomCC7 changed the base branch from cc/fix/cdr-native-sdk to cc/feat/cdr-unit-transports October 23, 2026 08:47
@TomCC7
TomCC7 added this pull request to stack #4799 October 23, 2026 08:47
@TomCC7
TomCC7 removed this pull request from stack #4799 October 23, 2026 09:19
@TomCC7
TomCC7 added this pull request to stack #4804 October 23, 2026 09:19

This branch was successfully deployed

No deployments
cachix — 7dacb4a9 Deployed Oct 23, 2026 by TomCC7 via cachix-build #14105
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant