Convenience wrappers for ROS2 publishers and action servers.
Latest Version: 1.0.0
Size: 23 KB
Publisher: ProtoTwin (contact@prototwin.com)
Convenience wrappers for publishing common ROS2 messages and executing robot trajectories from ProtoTwin. ROS2 nodes and wrappers should be owned by a scripted component. They can be created directly as instance fields.
import { type Entity, Component, ROS2 } from "prototwin";
import { PosePublisher, SimulationTimePublisher } from "@prototwin/ros2-wrappers";
export class ROSPublishers extends Component {
#node = ROS2.createNode("prototwin");
#timePublisher = this.#node.create(SimulationTimePublisher, this.world);
#posePublisher = this.#node.create(PosePublisher, { entity: this.entity });
public constructor(entity: Entity) {
super(entity);
}
}Wrappers can be created through ROS2.Node.create(). The node owns the wrapper and disposes it when the node is disposed.
Publishers with an autopublish option publish after every simulation timestep by default. Set autopublish to false to publish manually with publish(). Publisher options also accept ROS2 Quality of Service (QoS) settings and, except where noted, a custom topic name.
The SimulationTimePublisher wrapper publishes the current simulation time as rosgraph_msgs/msg/Clock.
/clocktopicName, qos, autopublishpublish()const clock = node.create(SimulationTimePublisher, world);The JointStatePublisher wrapper publishes the joints controlled by a RobotControllerComponent as sensor_msgs/msg/JointState. Each message contains the joint names, positions, velocities and efforts. Unnamed joints use J1, J2, and so on.
/joint_statescontroller, topicName, qos, autopublishpublish(stamp?)The controller can also be assigned through the controller property. When used by a component, you must assign the controller during initialization because other components may not yet be attached when its constructor runs.
const jointStates = node.create(JointStatePublisher, { controller: robotController });The PosePublisher wrapper publishes an entity pose as geometry_msgs/msg/PoseStamped. The pose is expressed relative to the optional base entity, or in the world frame when no base is assigned.
/poseentity, base, topicName, qos, autopublishpublish(stamp?)const pose = node.create(PosePublisher, { entity: target });The EntityTransformPublisher wrapper publishes entity transforms as tf2_msgs/msg/TFMessage.
/tfentity, transforms, topicName, qos, autopublishpublish(stamp?)Use EntityTransformFlags to publish the selected entity, its descendants, or both.
const transforms = node.create(EntityTransformPublisher, {
entity: robotBase,
transforms: EntityTransformFlags.IncludeEntityAndDescendants
});The StaticEntityTransformPublisher wrapper publishes static entity transforms as tf2_msgs/msg/TFMessage. It uses /tf_static with transient-local durability by default.
/tf_staticentity, transforms, topicName, qospublish(stamp?)Static transforms are published only when publish() is called.
const transforms = node.create(StaticEntityTransformPublisher, {
entity: sensorMount,
transforms: EntityTransformFlags.IncludeEntityAndDescendants
});
transforms.publish();The OdometryPublisher wrapper publishes an entity pose and velocity as nav_msgs/msg/Odometry. The pose is expressed relative to the optional base entity, or in the world frame when no base is assigned. Velocities are read from the physics body when available and otherwise estimated from changes in the transform.
/odomentity, base, topicName, qos, autopublishpublish(stamp?)const odometry = node.create(OdometryPublisher, { entity: mobileChassis });The FollowJointTrajectoryActionServer wrapper exposes a control_msgs/action/FollowJointTrajectory action server backed by a RobotControllerComponent instance.
The server validates joint names and trajectory positions, forwards path and goal tolerances to the robot controller, reports execution feedback, and supports cancellation. Only one trajectory can execute at a time.
controller, qos, validateLimitsThe controller can also be assigned through the controller property. When used by a component, you must assign the controller during initialization because other components may not yet be attached when its constructor runs.
const trajectoryServer = node.create(FollowJointTrajectoryActionServer, "/arm_controller/follow_joint_trajectory", {
controller: robotController,
validateLimits: true
});The ImagePublisher wrapper renders from the perspective of an entity's pose using the ROS camera convention and publishes sensor_msgs/msg/Image.
/camera/image_rawentity, width, height, intrinsics, near, far, tonemapping, format, topicName, qos, autopublishpublish()The message frame ID is the camera entity name, or world when no entity is assigned.
const image = node.create(ImagePublisher, world, { entity: cameraEntity });The CameraPublisher wrapper renders from the perspective of an entity's pose using the ROS camera convention and publishes synchronized sensor_msgs/msg/Image and sensor_msgs/msg/CameraInfo messages. Use this wrapper when consumers also require camera calibration data.
/camera/camera/image_raw and /camera/camera_infoname, entity, width, height, intrinsics, near, far, tonemapping, format, qos, autopublishCameraImageFormat.RGBA8, CameraImageFormat.RGB8 and CameraImageFormat.BGR8publish()The message frame ID is the camera entity name, or world when no entity is assigned.
const camera = node.create(CameraPublisher, world, {
name: "/front_camera",
entity: cameraEntity,
format: CameraImageFormat.RGB8
});Initial release.