1use crate::messages::Message;
37use std::marker::PhantomData;
38
39pub struct Topic<T: Message> {
93 path: &'static str,
94 _phantom: PhantomData<T>,
95}
96
97impl<T: Message> Topic<T> {
98 pub const fn new(path: &'static str) -> Self {
102 Self {
103 path,
104 _phantom: PhantomData,
105 }
106 }
107
108 pub const fn path(&self) -> &'static str {
110 self.path
111 }
112
113 pub fn as_str(&self) -> &'static str {
115 self.path
116 }
117}
118
119impl<T: Message> Clone for Topic<T> {
120 fn clone(&self) -> Self {
121 *self
122 }
123}
124
125impl<T: Message> Copy for Topic<T> {}
126
127impl<T: Message> std::fmt::Debug for Topic<T> {
128 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
129 write!(f, "Topic<{}>({:?})", std::any::type_name::<T>(), self.path)
130 }
131}
132
133impl<T: Message> std::fmt::Display for Topic<T> {
134 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
135 write!(f, "{}", self.path)
136 }
137}
138
139pub mod sensor {
145 use super::*;
146 use crate::messages::{GpsData, Image, Imu, JointState, LaserScan, Odometry};
147
148 pub const CAMERA_RGB: Topic<Image> = Topic::new("/sensor/camera/rgb");
150
151 pub const CAMERA_DEPTH: Topic<Image> = Topic::new("/sensor/camera/depth");
153
154 pub const CAMERA_LEFT: Topic<Image> = Topic::new("/sensor/camera/left");
156
157 pub const CAMERA_RIGHT: Topic<Image> = Topic::new("/sensor/camera/right");
159
160 pub const CAMERA_IR: Topic<Image> = Topic::new("/sensor/camera/ir");
162
163 pub const CAMERA_THERMAL: Topic<Image> = Topic::new("/sensor/camera/thermal");
165
166 pub const LIDAR_SCAN: Topic<LaserScan> = Topic::new("/sensor/lidar/scan");
168
169 pub const LIDAR_CLOUD: Topic<Vec<[f32; 3]>> = Topic::new("/sensor/lidar/cloud");
171
172 pub const ODOMETRY: Topic<Odometry> = Topic::new("/sensor/odometry");
174
175 pub const WHEEL_ODOM: Topic<Odometry> = Topic::new("/sensor/wheel_odom");
177
178 pub const VISUAL_ODOM: Topic<Odometry> = Topic::new("/sensor/visual_odom");
180
181 pub const IMU: Topic<Imu> = Topic::new("/sensor/imu");
183
184 pub const IMU_RAW: Topic<Imu> = Topic::new("/sensor/imu/raw");
186
187 pub const GPS: Topic<GpsData> = Topic::new("/sensor/gps");
189
190 pub const JOINT_STATES: Topic<JointState> = Topic::new("/sensor/joint_states");
192
193 pub const BATTERY_VOLTAGE: Topic<f32> = Topic::new("/sensor/battery/voltage");
195
196 pub const BATTERY_PERCENT: Topic<f32> = Topic::new("/sensor/battery/percent");
198
199 pub const TEMPERATURE: Topic<f32> = Topic::new("/sensor/temperature");
201
202 pub const ULTRASONIC: Topic<Vec<f32>> = Topic::new("/sensor/ultrasonic");
204
205 pub const CONTACT: Topic<Vec<bool>> = Topic::new("/sensor/contact");
207
208 pub const FORCE_TORQUE: Topic<[f32; 6]> = Topic::new("/sensor/force_torque");
210}
211
212pub mod actuator {
214 use super::*;
215 use crate::messages::{GripperCommand, JointState, Twist};
216
217 pub const CMD_VEL: Topic<Twist> = Topic::new("/actuator/cmd_vel");
219
220 pub const CMD_VEL_RAW: Topic<Twist> = Topic::new("/actuator/cmd_vel/raw");
222
223 pub const CMD_VEL_TELEOP: Topic<Twist> = Topic::new("/actuator/cmd_vel/teleop");
225
226 pub const CMD_VEL_NAV: Topic<Twist> = Topic::new("/actuator/cmd_vel/nav");
228
229 pub const JOINT_CMD: Topic<JointState> = Topic::new("/actuator/joint_cmd");
231
232 pub const JOINT_TRAJECTORY: Topic<Vec<JointState>> = Topic::new("/actuator/joint_trajectory");
234
235 pub const GRIPPER_CMD: Topic<GripperCommand> = Topic::new("/actuator/gripper_cmd");
237
238 pub const MOTOR_CMD: Topic<Vec<f32>> = Topic::new("/actuator/motor_cmd");
240
241 pub const LIGHTS: Topic<Vec<u8>> = Topic::new("/actuator/lights");
243
244 pub const SOUND: Topic<String> = Topic::new("/actuator/sound");
246
247 pub const EMERGENCY_STOP: Topic<bool> = Topic::new("/actuator/emergency_stop");
249
250 pub const ENABLE: Topic<bool> = Topic::new("/actuator/enable");
252}
253
254pub mod manipulation {
256 use super::*;
257 use crate::messages::{GripperCommand, JointState, Pose};
258
259 pub const EE_POSE_TARGET: Topic<Pose> = Topic::new("/manipulation/ee_pose_target");
261
262 pub const EE_POSE: Topic<Pose> = Topic::new("/manipulation/ee_pose");
264
265 pub const CARTESIAN_PATH: Topic<Vec<Pose>> = Topic::new("/manipulation/cartesian_path");
267
268 pub const TRAJECTORY: Topic<Vec<JointState>> = Topic::new("/manipulation/trajectory");
270
271 pub const GRIPPER: Topic<GripperCommand> = Topic::new("/manipulation/gripper");
273
274 pub const GRASP_POSES: Topic<Vec<Pose>> = Topic::new("/manipulation/grasp_poses");
276
277 pub const SELECTED_GRASP: Topic<Pose> = Topic::new("/manipulation/selected_grasp");
279
280 pub const STATUS: Topic<String> = Topic::new("/manipulation/status");
282}
283
284pub mod perception {
286 use super::*;
287 use crate::messages::{Detection, Image, Pose};
288
289 pub const DETECTIONS: Topic<Vec<Detection>> = Topic::new("/perception/detections");
291
292 pub const HIGH_CONFIDENCE: Topic<Vec<Detection>> = Topic::new("/perception/detections/high_confidence");
294
295 pub const PEOPLE: Topic<Vec<Detection>> = Topic::new("/perception/detections/people");
297 pub const OBJECTS: Topic<Vec<Detection>> = Topic::new("/perception/detections/objects");
298 pub const OBSTACLES: Topic<Vec<Detection>> = Topic::new("/perception/detections/obstacles");
299
300 pub const TRACKING: Topic<Vec<Detection>> = Topic::new("/perception/tracking");
302
303 pub const POSE: Topic<Pose> = Topic::new("/perception/pose");
305
306 pub const LOCALIZATION_QUALITY: Topic<f32> = Topic::new("/perception/localization/quality");
308
309 pub const SEGMENTATION: Topic<Image> = Topic::new("/perception/segmentation");
311
312 pub const DEPTH_ESTIMATION: Topic<Image> = Topic::new("/perception/depth");
314
315 pub const OPTICAL_FLOW: Topic<Vec<[f32; 2]>> = Topic::new("/perception/optical_flow");
317
318 pub const FEATURES: Topic<Vec<[f32; 2]>> = Topic::new("/perception/features");
320
321 pub const SCENE_TYPE: Topic<String> = Topic::new("/perception/scene_type");
323}
324
325pub mod navigation {
327 use super::*;
328 use crate::messages::{NavigationGoal, NavigationStatus, Path, Pose, Twist};
329
330 pub const GOAL: Topic<NavigationGoal> = Topic::new("/navigation/goal");
332
333 pub const CANCEL: Topic<bool> = Topic::new("/navigation/cancel");
335
336 pub const STATUS: Topic<NavigationStatus> = Topic::new("/navigation/status");
338
339 pub const GLOBAL_PATH: Topic<Path> = Topic::new("/navigation/global_path");
341
342 pub const LOCAL_PATH: Topic<Path> = Topic::new("/navigation/local_path");
344
345 pub const CURRENT_WAYPOINT: Topic<crate::messages::Waypoint> = Topic::new("/navigation/waypoint");
347
348 pub const NEXT_WAYPOINT: Topic<crate::messages::Waypoint> = Topic::new("/navigation/next_waypoint");
350
351 pub const GLOBAL_COSTMAP: Topic<Vec<Vec<u8>>> = Topic::new("/navigation/costmap/global");
353
354 pub const LOCAL_COSTMAP: Topic<Vec<Vec<u8>>> = Topic::new("/navigation/costmap/local");
356
357 pub const RECOVERY: Topic<String> = Topic::new("/navigation/recovery");
359
360 pub const DYNAMIC_OBSTACLES: Topic<Vec<Pose>> = Topic::new("/navigation/obstacles/dynamic");
362
363 pub const STATIC_OBSTACLES: Topic<Vec<Pose>> = Topic::new("/navigation/obstacles/static");
365
366 pub const VELOCITY_LIMITS: Topic<Twist> = Topic::new("/navigation/velocity_limits");
368}
369
370pub mod mapping {
372 use super::*;
373 use crate::messages::Pose;
374
375 pub const OCCUPANCY_GRID: Topic<Vec<Vec<u8>>> = Topic::new("/mapping/occupancy_grid");
377
378 pub const MAP_METADATA: Topic<String> = Topic::new("/mapping/metadata");
380
381 pub const LOOP_CLOSURES: Topic<Vec<(Pose, Pose)>> = Topic::new("/mapping/loop_closures");
383
384 pub const MAP_UPDATE: Topic<Vec<(usize, usize, u8)>> = Topic::new("/mapping/update");
386
387 pub const SAVE_MAP: Topic<String> = Topic::new("/mapping/save");
389
390 pub const LOAD_MAP: Topic<String> = Topic::new("/mapping/load");
392
393 pub const STATUS: Topic<String> = Topic::new("/mapping/status");
395}
396
397pub mod system {
399 use super::*;
400 use crate::messages::{HealthStatus, NodeStatus, SystemCommand};
401
402 pub const HEALTH: Topic<HealthStatus> = Topic::new("/system/health");
404
405 pub const NODE_STATUS: Topic<NodeStatus> = Topic::new("/system/node_status");
407
408 pub const COMMAND: Topic<SystemCommand> = Topic::new("/system/command");
410
411 pub const HEARTBEAT: Topic<u64> = Topic::new("/system/heartbeat");
413
414 pub const DIAGNOSTICS: Topic<String> = Topic::new("/system/diagnostics");
416
417 pub const WARNINGS: Topic<Vec<String>> = Topic::new("/system/warnings");
419
420 pub const ERRORS: Topic<Vec<String>> = Topic::new("/system/errors");
422
423 pub const CPU_USAGE: Topic<f32> = Topic::new("/system/cpu_usage");
425
426 pub const MEMORY_USAGE: Topic<f32> = Topic::new("/system/memory_usage");
428
429 pub const DISK_USAGE: Topic<f32> = Topic::new("/system/disk_usage");
431
432 pub const NETWORK_STATS: Topic<(f32, f32)> = Topic::new("/system/network_stats"); pub const SYSTEM_TEMP: Topic<f32> = Topic::new("/system/temperature");
437
438 pub const POWER: Topic<f32> = Topic::new("/system/power");
440
441 pub const UPTIME: Topic<u64> = Topic::new("/system/uptime");
443
444 pub const LOG: Topic<String> = Topic::new("/system/log");
446
447 pub const CONFIG_UPDATE: Topic<String> = Topic::new("/system/config_update");
449
450 pub const SHUTDOWN: Topic<bool> = Topic::new("/system/shutdown");
452
453 pub const REBOOT: Topic<bool> = Topic::new("/system/reboot");
455}
456
457pub mod teleop {
459 use super::*;
460 use crate::messages::Twist;
461
462 pub const JOYSTICK: Topic<Vec<f32>> = Topic::new("/teleop/joystick");
464
465 pub const BUTTONS: Topic<Vec<bool>> = Topic::new("/teleop/buttons");
467
468 pub const CMD_VEL: Topic<Twist> = Topic::new("/teleop/cmd_vel");
470
471 pub const ENABLE: Topic<bool> = Topic::new("/teleop/enable");
473
474 pub const MODE: Topic<String> = Topic::new("/teleop/mode");
476}
477
478pub mod ml {
480 use super::*;
481 use crate::messages::Detection;
482
483 pub const INFERENCE_REQUEST: Topic<Vec<u8>> = Topic::new("/ml/inference/request");
485
486 pub const INFERENCE_RESULT: Topic<Vec<f32>> = Topic::new("/ml/inference/result");
488
489 pub const PREDICTIONS: Topic<Vec<Detection>> = Topic::new("/ml/predictions");
491
492 pub const METRICS: Topic<String> = Topic::new("/ml/metrics");
494
495 pub const LOAD_MODEL: Topic<String> = Topic::new("/ml/load_model");
497
498 pub const MODEL_INFO: Topic<String> = Topic::new("/ml/model_info");
500
501 pub const TRAINING_DATA: Topic<Vec<u8>> = Topic::new("/ml/training_data");
503}
504
505#[macro_export]
522macro_rules! topic_group {
523 (
524 $(#[$meta:meta])*
525 $vis:vis struct $name:ident {
526 $(
527 $field:ident: $topic:expr
528 ),* $(,)?
529 }
530 ) => {
531 $(#[$meta])*
532 $vis struct $name;
533
534 impl $name {
535 pub fn topics() -> Vec<&'static str> {
537 vec![
538 $( $topic.path() ),*
539 ]
540 }
541 }
542 };
543}
544
545topic_group! {
547 pub struct AllSensors {
549 camera_rgb: sensor::CAMERA_RGB,
550 camera_depth: sensor::CAMERA_DEPTH,
551 lidar: sensor::LIDAR_SCAN,
552 odometry: sensor::ODOMETRY,
553 imu: sensor::IMU,
554 gps: sensor::GPS,
555 }
556}
557
558topic_group! {
559 pub struct AllActuators {
561 cmd_vel: actuator::CMD_VEL,
562 joint_cmd: actuator::JOINT_CMD,
563 gripper: actuator::GRIPPER_CMD,
564 }
565}
566
567pub struct SubscribeBuilder<T: Message> {
583 #[allow(dead_code)]
584 topic: Topic<T>,
585 #[allow(dead_code)]
586 buffer_size: Option<usize>,
587 #[allow(dead_code)]
588 latest_only: bool,
589 #[allow(dead_code)]
590 throttle_ms: Option<u64>,
591}
592
593impl<T: Message> SubscribeBuilder<T> {
594 pub fn new(topic: Topic<T>) -> Self {
595 Self {
596 topic,
597 buffer_size: None,
598 latest_only: false,
599 throttle_ms: None,
600 }
601 }
602
603 pub fn buffer(mut self, size: usize) -> Self {
605 self.buffer_size = Some(size);
606 self
607 }
608
609 pub fn latest(mut self) -> Self {
611 self.latest_only = true;
612 self.buffer_size = Some(1);
613 self
614 }
615
616 pub fn throttle(mut self, duration: std::time::Duration) -> Self {
618 self.throttle_ms = Some(duration.as_millis() as u64);
619 self
620 }
621
622 }
624
625pub struct PublishBuilder<'a, T: Message> {
634 #[allow(dead_code)]
635 topic: Topic<T>,
636 #[allow(dead_code)]
637 message: &'a T,
638 #[allow(dead_code)]
639 #[allow(clippy::type_complexity)]
640 condition: Option<Box<dyn Fn(&T) -> bool>>,
641 #[allow(dead_code)]
642 throttle_ms: Option<u64>,
643}
644
645impl<'a, T: Message> PublishBuilder<'a, T> {
646 pub fn new(topic: Topic<T>, message: &'a T) -> Self {
647 Self {
648 topic,
649 message,
650 condition: None,
651 throttle_ms: None,
652 }
653 }
654
655 pub fn when<F>(mut self, condition: F) -> Self
657 where
658 F: Fn(&T) -> bool + 'static,
659 {
660 self.condition = Some(Box::new(condition));
661 self
662 }
663
664 pub fn throttle(mut self, duration: std::time::Duration) -> Self {
666 self.throttle_ms = Some(duration.as_millis() as u64);
667 self
668 }
669
670 }
672
673