Skip to content

Added external pose input message and pose relay script.#393

Merged
kcleineweber merged 3 commits intomasterfrom
ext-pose
Mar 2, 2026
Merged

Added external pose input message and pose relay script.#393
kcleineweber merged 3 commits intomasterfrom
ext-pose

Conversation

@kcleineweber
Copy link
Contributor

New Features

  • Added new ExternalPoseInput message definition.
  • Added pose_relay.py example script for transmitting pose from a source device to a target device.

* measurements generated by an external source (for example, another Point One
* device or a vision system).
*
* Position and velocity are expressed in the ECEF frame. @ref position_ecef_m
Copy link
Collaborator

Choose a reason for hiding this comment

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

Velocity is in ENU

case MessageType::VEHICLE_TICK_INPUT:
case MessageType::WHEEL_SPEED_INPUT:
case MessageType::VEHICLE_SPEED_INPUT:
case MessageType::EXTERNAL_POSE_INPUT:
Copy link
Collaborator

Choose a reason for hiding this comment

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

Keep in the same order as the enum definition please

Copy link
Collaborator

@adamshapiro0 adamshapiro0 left a comment

Choose a reason for hiding this comment

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

Looks good, couple minor things

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR introduces a new FusionEngine measurement input (ExternalPoseInput) for supplying externally generated pose/velocity to a device, along with a Python example (pose_relay.py) that relays/transforms pose from a source device to a target device.

Changes:

  • Added ExternalPoseInput message definition in both C++ and Python, and registered the new MessageType value.
  • Added pose_relay.py example script to transform/forward pose between devices using lever arms and mounting offsets.
  • Added Euler/DCM helper utilities in analysis/attitude.py and updated a Python test to include the new message type in wildcard matching.

Reviewed changes

Copilot reviewed 7 out of 7 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
src/point_one/fusion_engine/messages/measurements.h Adds the C++ ExternalPoseInput payload definition and documentation.
src/point_one/fusion_engine/messages/defs.h Registers the new message type, string name, and command classification.
python/fusion_engine_client/messages/defs.py Registers the new Python MessageType enum value.
python/fusion_engine_client/messages/measurements.py Adds Python ExternalPoseInput pack/unpack and numpy conversion support.
python/fusion_engine_client/analysis/attitude.py Adds DCM/Euler conversion helpers used by the relay example.
python/examples/pose_relay.py New example script to receive PoseMessage, transform it, and send ExternalPoseInput.
python/tests/test_message_defs.py Updates wildcard matching expectation to include EXTERNAL_POSE_INPUT.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +1280 to +1284
* Position and velocity are expressed in the ECEF frame. @ref position_ecef_m
* should correspond to the output lever arm point configured on the receiving
* device (see @ref ConfigType::OUTPUT_LEVER_ARM), so that the position matches
* the point the device will report in its own @ref PoseMessage after
* initialization.
Copy link

Copilot AI Mar 1, 2026

Choose a reason for hiding this comment

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

The struct-level doc says “Position and velocity are expressed in the ECEF frame”, but this message’s velocity fields are explicitly ENU (velocity_enu_mps, velocity_std_enu_mps). Please correct the description so it matches the actual field definitions (ECEF position, ENU velocity).

Suggested change
* Position and velocity are expressed in the ECEF frame. @ref position_ecef_m
* should correspond to the output lever arm point configured on the receiving
* device (see @ref ConfigType::OUTPUT_LEVER_ARM), so that the position matches
* the point the device will report in its own @ref PoseMessage after
* initialization.
* Position is expressed in the ECEF frame (see @ref position_ecef_m) and
* velocity is expressed in the local ENU frame (see @ref velocity_enu_mps).
* @ref position_ecef_m should correspond to the output lever arm point
* configured on the receiving device (see @ref ConfigType::OUTPUT_LEVER_ARM),
* so that the position matches the point the device will report in its own
* @ref PoseMessage after initialization.

Copilot uses AI. Check for mistakes.
Comment on lines +99 to +109
# We will still pass through NAN attitude, but for the sake of
# the position transformation we can treat it as no rotation.
ypr_enu_to_sb_deg = source_pose_msg.ypr_deg
if np.any(np.isnan(ypr_enu_to_sb_deg)):
rotmat_enu_to_sb = np.eye(3)
else:
rotmat_enu_to_sb = euler_angles_to_dcm(
ypr_enu_to_sb_deg[::-1], order='321', deg=True)
rotmat_sb_to_enu = rotmat_enu_to_sb.T
rotmat_sb_to_ecef = rotmat_enu_to_ecef @ rotmat_sb_to_enu

Copy link

Copilot AI Mar 1, 2026

Choose a reason for hiding this comment

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

When the source YPR contains NaNs, the code treats the ENU->source-body rotation as identity for position lever-arm corrections. If either lever arm/translation/target rotation is non-zero, this produces an incorrect target ECEF position (and later velocity) rather than “no rotation”. Consider skipping forwarding until attitude is valid (or only allowing zero lever arms/transforms when attitude is NaN), and emit a clear warning to the user.

Copilot uses AI. Check for mistakes.
Comment on lines +183 to +190
# Timestamps.
if source_pose_msg.gps_time:
ext_pose.details.measurement_time = source_pose_msg.gps_time
ext_pose.details.measurement_time_source = SystemTimeSource.GPS_TIME
else:
ext_pose.details.measurement_time = Timestamp()
ext_pose.details.measurement_time_source = SystemTimeSource.INVALID

Copy link

Copilot AI Mar 1, 2026

Choose a reason for hiding this comment

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

If GPS time isn’t available, the script currently sends measurement_time_source=INVALID with an invalid timestamp. Since PoseMessage always includes p1_time, it would be more accurate to set measurement_time=source_pose_msg.p1_time and measurement_time_source=SystemTimeSource.P1_TIME so the target can preserve the source measurement time base instead of using arrival time.

Copilot uses AI. Check for mistakes.
Comment on lines +410 to +417
source_transport.close()
target_transport.close()

except KeyboardInterrupt:
pass
finally:
elapsed = (datetime.now() - start_time).total_seconds()
logger.info(f"Done. Pose relayed in {elapsed:.1f} seconds.")
Copy link

Copilot AI Mar 1, 2026

Choose a reason for hiding this comment

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

source_transport.close() / target_transport.close() are only called on the normal (non-interrupted) path. On KeyboardInterrupt (or any other exception), the transports may remain open until process exit. Consider moving the close calls into the finally block (guarded by if transport is not None) to ensure cleanup.

Copilot uses AI. Check for mistakes.
@kcleineweber kcleineweber merged commit 4e64146 into master Mar 2, 2026
17 checks passed
@kcleineweber kcleineweber deleted the ext-pose branch March 2, 2026 21:09
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.

3 participants