From 559304d4aa9d71fa22db9447a57f3c29c5884739 Mon Sep 17 00:00:00 2001 From: Minggang Wang Date: Thu, 17 Jul 2025 12:55:12 +0800 Subject: [PATCH] Add README for each example --- example/actions/README.md | 299 +++++++++++++++++++++ example/graph/README.md | 306 +++++++++++++++++++++ example/lifecycle/README.md | 397 +++++++++++++++++++++++++++ example/parameter/README.md | 316 ++++++++++++++++++++++ example/rate/README.md | 391 +++++++++++++++++++++++++++ example/rosidl/README.md | 519 ++++++++++++++++++++++++++++++++++++ example/services/README.md | 179 +++++++++++++ example/topics/README.md | 204 ++++++++++++++ 8 files changed, 2611 insertions(+) create mode 100644 example/actions/README.md create mode 100644 example/graph/README.md create mode 100644 example/lifecycle/README.md create mode 100644 example/parameter/README.md create mode 100644 example/rate/README.md create mode 100644 example/rosidl/README.md create mode 100644 example/services/README.md create mode 100644 example/topics/README.md diff --git a/example/actions/README.md b/example/actions/README.md new file mode 100644 index 00000000..dc4aa521 --- /dev/null +++ b/example/actions/README.md @@ -0,0 +1,299 @@ +# ROS 2 Actions Examples + +This directory contains examples demonstrating ROS 2 action-based communication using rclnodejs. Actions provide a more complex communication pattern than topics or services, designed for long-running tasks that require feedback and can be canceled. + +## Overview + +ROS 2 actions are built on top of topics and services to provide: + +- **Goal**: A request to perform some long-running task +- **Feedback**: Periodic updates on progress during task execution +- **Result**: The final outcome when the task completes +- **Cancellation**: Ability to stop the task before completion + +Actions are ideal for: + +- Navigation tasks (moving a robot to a location) +- Long-running computations +- Tasks that need progress updates +- Operations that might need to be canceled + +## Action Examples + +### Action Client Examples + +The `action_client/` directory contains examples of nodes that send goals to action servers: + +#### 1. Basic Action Client (`action-client-example.js`) + +**Purpose**: Demonstrates basic action client functionality with the Fibonacci action. + +- **Action Type**: `test_msgs/action/Fibonacci` +- **Action Name**: `fibonacci` +- **Functionality**: + - Sends a goal to compute Fibonacci sequence up to order 10 + - Receives and logs feedback during execution + - Waits for final result and logs success/failure + - Automatically shuts down after completion +- **Features**: + - Server availability checking with `waitForServer()` + - Goal acceptance verification + - Feedback handling during execution + - Result processing with status checking +- **Run Command**: `node action_client/action-client-example.js` + +#### 2. Action Client with Cancellation (`action-client-cancel-example.js`) + +**Purpose**: Demonstrates how to cancel an action goal during execution. + +- **Action Type**: `test_msgs/action/Fibonacci` +- **Action Name**: `fibonacci` +- **Functionality**: + - Sends a goal to compute Fibonacci sequence up to order 10 + - Receives feedback for 2 seconds + - Automatically cancels the goal after timer expires + - Logs cancellation success/failure +- **Features**: + - Timer-based cancellation mechanism + - Goal cancellation with `cancelGoal()` + - Cancellation response verification + - Cleanup and shutdown handling +- **Run Command**: `node action_client/action-client-cancel-example.js` + +### Action Server Examples + +The `action_server/` directory contains examples of nodes that provide action services: + +#### 1. Basic Action Server (`action-server-example.js`) + +**Purpose**: Demonstrates basic action server implementation for computing Fibonacci sequences. + +- **Action Type**: `test_msgs/action/Fibonacci` +- **Action Name**: `fibonacci` +- **Functionality**: + - Accepts goals to compute Fibonacci sequences + - Publishes feedback with incremental sequence updates + - Returns complete sequence as result + - Supports goal cancellation during execution +- **Features**: + - Goal acceptance callback (`goalCallback`) + - Execution callback with feedback publishing + - Cancellation handling (`cancelCallback`) + - Progress updates every second +- **Run Command**: `node action_server/action-server-example.js` + +#### 2. Deferred Execution Server (`action-server-defer-example.js`) + +**Purpose**: Shows how to defer goal execution using timers and handle accepted callbacks. + +- **Action Type**: `test_msgs/action/Fibonacci` +- **Action Name**: `fibonacci` +- **Functionality**: + - Accepts goals immediately but defers execution for 3 seconds + - Uses timer to delay goal execution start + - Demonstrates deferred execution pattern + - Same Fibonacci computation as basic server +- **Features**: + - Goal acceptance with deferred execution + - Handle accepted callback (`handleAcceptedCallback`) + - Timer-based execution control + - Manual goal execution triggering +- **Run Command**: `node action_server/action-server-defer-example.js` + +#### 3. Single Goal Server (`action-server-single-goal-example.js`) + +**Purpose**: Demonstrates a server that only allows one active goal at a time. + +- **Action Type**: `test_msgs/action/Fibonacci` +- **Action Name**: `fibonacci` +- **Functionality**: + - Accepts new goals but aborts any currently active goal + - Ensures only one goal executes at a time + - Tracks active goal state + - Same Fibonacci computation with single-goal constraint +- **Features**: + - Single goal enforcement + - Automatic abortion of previous goals + - Goal state tracking (`isActive`) + - Handle accepted callback for goal management +- **Run Command**: `node action_server/action-server-single-goal-example.js` + +## How to Run the Examples + +### Prerequisites + +1. Ensure ROS 2 is installed and sourced +2. Navigate to the `example/actions` directory + +### Running Complete Action Examples + +#### Basic Action Communication + +1. **Start the Action Server**: In one terminal, run: + + ```bash + cd example/actions + node action_server/action-server-example.js + ``` + + You should see: + + ``` + [INFO] [action_server_example_node]: Action server started + ``` + +2. **Start the Action Client**: In another terminal, run: + + ```bash + cd example/actions + node action_client/action-client-example.js + ``` + +3. **Expected Output**: + + **Action Server Terminal**: + + ``` + [INFO] [action_server_example_node]: Received goal request + [INFO] [action_server_example_node]: Executing goal... + [INFO] [action_server_example_node]: Publishing feedback: 0,1 + [INFO] [action_server_example_node]: Publishing feedback: 0,1,1 + [INFO] [action_server_example_node]: Publishing feedback: 0,1,1,2 + ... + [INFO] [action_server_example_node]: Returning result: 0,1,1,2,3,5,8,13,21,34,55 + ``` + + **Action Client Terminal**: + + ``` + [INFO] [action_client_example_node]: Waiting for action server... + [INFO] [action_client_example_node]: Sending goal request... + [INFO] [action_client_example_node]: Goal accepted + [INFO] [action_client_example_node]: Received feedback: 0,1 + [INFO] [action_client_example_node]: Received feedback: 0,1,1 + ... + [INFO] [action_client_example_node]: Goal succeeded with result: 0,1,1,2,3,5,8,13,21,34,55 + ``` + +#### Action Cancellation Example + +1. **Start Server**: Run any action server example +2. **Start Cancellation Client**: + ```bash + node action_client/action-client-cancel-example.js + ``` +3. **Expected Behavior**: Client sends goal, receives feedback for 2 seconds, then cancels + +#### Specialized Server Examples + +- **Deferred Execution**: Use `action-server-defer-example.js` to see 3-second execution delay +- **Single Goal**: Use `action-server-single-goal-example.js` to test goal abortion behavior + +## Action Components Explained + +### Action Message Structure + +The `test_msgs/action/Fibonacci` action consists of: + +``` +# Goal +int32 order +--- +# Result +int32[] sequence +--- +# Feedback +int32[] sequence +``` + +### Key Concepts Demonstrated + +#### Action Client Concepts + +- **Goal Sending**: Using `sendGoal()` with feedback callbacks +- **Server Discovery**: Waiting for action servers with `waitForServer()` +- **Goal Status**: Checking acceptance and completion status +- **Feedback Handling**: Processing incremental updates during execution +- **Result Processing**: Handling final results and status +- **Goal Cancellation**: Canceling active goals with `cancelGoal()` + +#### Action Server Concepts + +- **Goal Callbacks**: Accepting or rejecting incoming goals +- **Execution Callbacks**: Implementing the actual action logic +- **Feedback Publishing**: Sending progress updates to clients +- **Result Handling**: Returning final results upon completion +- **Cancellation Support**: Responding to cancellation requests +- **Goal State Management**: Tracking active goals and their status + +#### Advanced Patterns + +- **Deferred Execution**: Accepting goals but delaying execution start +- **Single Goal Servers**: Limiting concurrent goal execution +- **Goal Abortion**: Terminating active goals when new ones arrive +- **Timer Integration**: Using ROS timers for delayed operations + +## Programming Patterns + +### Class-Based Architecture + +All examples use ES6 classes to encapsulate action functionality: + +- Clean separation of client/server logic +- Proper callback binding with `bind(this)` +- State management through instance variables + +### Asynchronous Operations + +- **Async/Await**: Modern JavaScript patterns for goal handling +- **Promises**: Integration with ROS action lifecycle +- **Callbacks**: Feedback and result processing + +### Resource Management + +- **Timer Cleanup**: Proper timer cancellation +- **Goal Tracking**: Maintaining references to active goals +- **Shutdown Handling**: Clean node shutdown after completion + +## Troubleshooting + +### Common Issues + +1. **Action Server Not Available**: + + - Ensure action server is running before starting client + - Check that both use the same action name (`fibonacci`) + - Verify action type matches (`test_msgs/action/Fibonacci`) + +2. **Goal Not Accepted**: + + - Check server's `goalCallback` return value + - Verify goal message structure is correct + - Ensure server is properly initialized + +3. **Missing Feedback**: + + - Confirm feedback callback is properly bound + - Check server's `publishFeedback()` calls + - Verify feedback message structure + +4. **Cancellation Issues**: + - Ensure server implements `cancelCallback` + - Check `isCancelRequested` in execution loop + - Verify proper `goalHandle.canceled()` calls + +### Debugging Tips + +- Use `ros2 action list` to see available actions +- Use `ros2 action info ` to check action details +- Use `ros2 action send_goal ` to test from command line +- Monitor action topics: `/_action/status`, `/_action/feedback`, `/_action/result` + +## Notes + +- All examples use the Fibonacci sequence computation as a representative long-running task +- Action servers run continuously until manually terminated (Ctrl+C) +- Action clients typically complete one goal cycle then exit +- Goals are processed with 1-second intervals to demonstrate feedback clearly +- Cancellation examples use timers to simulate real-world cancellation scenarios +- Single goal servers demonstrate resource management for concurrent requests diff --git a/example/graph/README.md b/example/graph/README.md new file mode 100644 index 00000000..8ceb4fda --- /dev/null +++ b/example/graph/README.md @@ -0,0 +1,306 @@ +# ROS 2 Graph Examples + +This directory contains examples demonstrating ROS 2 graph introspection using rclnodejs. These examples show how to programmatically discover and inspect the ROS 2 computational graph, including nodes, topics, services, and their relationships. + +## Overview + +The ROS 2 computational graph represents the runtime structure of a ROS 2 system, showing: + +- **Nodes**: Computational processes that perform specific tasks +- **Topics**: Named communication channels for publish-subscribe messaging +- **Services**: Request-response communication endpoints +- **Actions**: Long-running task coordination mechanisms +- **Parameters**: Configuration values for nodes + +Graph introspection allows you to: + +- Discover what nodes are running in the system +- Identify available topics and their message types +- Find services and their interfaces +- Understand the communication relationships between nodes +- Debug and monitor system architecture + +## Graph Example + +### ROS Graph Discovery (`ros-graph-example.js`) + +**Purpose**: Demonstrates comprehensive ROS 2 graph introspection capabilities. + +#### Functionality + +This example creates a complete ROS 2 system with multiple nodes and then introspects the graph to display: + +1. **Node Creation**: Creates several nodes with different communication patterns: + + - `publisher_node` (namespace: `ns1`) - publishes to a topic + - `subscriber_node` (namespace: `ns1`) - subscribes to a topic + - `service_node` (namespace: `ns1`) - provides a service + - `client_node` (namespace: `ns2`) - creates a service client + - `ros_graph_display_node` (namespace: `ns1`) - performs graph introspection + +2. **Graph Introspection**: Uses various discovery methods to analyze the system: + - Node discovery and namespace listing + - Topic discovery with message types + - Service discovery with service types + - Publisher-subscriber relationships by node + - Service-client relationships by node + +#### Communication Setup + +- **Topic Communication**: `publisher_node` and `subscriber_node` communicate via `topic` using `std_msgs/msg/String` +- **Service Communication**: `service_node` provides `add_two_ints` service using `example_interfaces/srv/AddTwoInts` +- **Cross-Namespace**: `client_node` in `ns2` can discover and connect to services in `ns1` + +#### Features Demonstrated + +- **Multi-namespace architecture**: Nodes in different namespaces (`ns1`, `ns2`) +- **Complete communication patterns**: Publishers, subscribers, services, clients +- **Graph discovery APIs**: All major introspection methods +- **Structured output**: JSON-formatted relationship mapping +- **Real-time introspection**: Live discovery of active system components + +#### Run Command + +```bash +node ros-graph-example.js +``` + +## Sample Output + +When you run the example, you'll see structured output showing the complete ROS 2 graph: + +### Expected Output Structure + +``` +This example creates the following nodes and outputs the corresponding ROS2 graph: + publisher_node + subscriber_node + service_node + ros_graph_display_node + +NODES +[ + '/ns1/publisher_node', + '/ns1/subscriber_node', + '/ns1/service_node', + '/ns2/client_node', + '/ns1/ros_graph_display_node' +] + +TOPICS & TYPES +[ + { name: '/ns1/topic', types: ['std_msgs/msg/String'] }, + { name: '/parameter_events', types: ['rcl_interfaces/msg/ParameterEvent'] }, + { name: '/rosout', types: ['rcl_interfaces/msg/Log'] } +] + +SERVICES & TYPES +[ + { name: '/ns1/add_two_ints', types: ['example_interfaces/srv/AddTwoInts'] }, + { name: '/ns1/publisher_node/describe_parameters', types: ['rcl_interfaces/srv/DescribeParameters'] }, + // ... other built-in node services +] + +PUBLISHERS BY NODE +[ + { + "node": { "name": "publisher_node", "namespace": "/ns1" }, + "info": [ + { "name": "/ns1/topic", "types": ["std_msgs/msg/String"] } + ] + }, + // ... other nodes +] + +SUBSCRIPTIONS BY NODE +[ + { + "node": { "name": "subscriber_node", "namespace": "/ns1" }, + "info": [ + { "name": "/ns1/topic", "types": ["std_msgs/msg/String"] } + ] + }, + // ... other nodes +] + +SERVICES BY NODE +[ + { + "node": { "name": "service_node", "namespace": "/ns1" }, + "info": [ + { "name": "/ns1/add_two_ints", "types": ["example_interfaces/srv/AddTwoInts"] } + ] + }, + // ... other nodes with built-in services +] + +CLIENTS BY NODE +[ + { "name": "/ns1/add_two_ints", "types": ["example_interfaces/srv/AddTwoInts"] } +] +``` + +## Key Graph Introspection APIs + +The example demonstrates these important rclnodejs graph discovery methods: + +### Node Discovery + +- **`getNodeNames()`**: Returns list of all node names in the system +- **`getNodeNamesAndNamespaces()`**: Returns nodes with their namespace information + +### Topic Discovery + +- **`getTopicNamesAndTypes()`**: Lists all topics with their message types +- **`getPublisherNamesAndTypesByNode(name, namespace)`**: Shows what topics a specific node publishes +- **`getSubscriptionNamesAndTypesByNode(name, namespace)`**: Shows what topics a specific node subscribes to + +### Service Discovery + +- **`getServiceNamesAndTypes()`**: Lists all services with their interface types +- **`getServiceNamesAndTypesByNode(name, namespace)`**: Shows what services a specific node provides +- **`getClientNamesAndTypesByNode(name, namespace)`**: Shows what services a specific node uses as a client + +## Understanding the Output + +### Namespace Organization + +- **`/ns1`**: Contains publisher, subscriber, service, and display nodes +- **`/ns2`**: Contains the client node +- **Cross-namespace communication**: Client in `ns2` can access services in `ns1` + +### Built-in Topics and Services + +ROS 2 automatically creates several system topics and services: + +- **`/parameter_events`**: Parameter change notifications +- **`/rosout`**: Logging messages +- **Node services**: Each node gets automatic parameter and lifecycle services + +### Communication Patterns + +- **Publisher-Subscriber**: `publisher_node` → `/ns1/topic` ← `subscriber_node` +- **Service-Client**: `client_node` → `/ns1/add_two_ints` ← `service_node` + +## Use Cases for Graph Introspection + +### System Monitoring + +```javascript +// Check if a specific node is running +const nodes = node.getNodeNames(); +const isNodeRunning = nodes.includes('/my_namespace/my_node'); + +// Monitor topic activity +const topics = node.getTopicNamesAndTypes(); +const activeTopics = topics.filter((topic) => + topic.name.startsWith('/sensors') +); +``` + +### Dynamic Service Discovery + +```javascript +// Find all available services +const services = node.getServiceNamesAndTypes(); +const mathServices = services.filter((service) => + service.types.includes('example_interfaces/srv/AddTwoInts') +); +``` + +### Architecture Validation + +```javascript +// Verify expected communication patterns +const nodeInfo = node.getNodeNamesAndNamespaces(); +for (const nodeData of nodeInfo) { + const publishers = node.getPublisherNamesAndTypesByNode( + nodeData.name, + nodeData.namespace + ); + const subscribers = node.getSubscriptionNamesAndTypesByNode( + nodeData.name, + nodeData.namespace + ); + // Validate expected patterns +} +``` + +## Comparing with ROS 2 CLI Tools + +The programmatic introspection shown in this example provides similar information to ROS 2 command-line tools: + +| rclnodejs Method | Equivalent ROS 2 CLI | +| -------------------------------------- | ----------------------- | +| `getNodeNames()` | `ros2 node list` | +| `getTopicNamesAndTypes()` | `ros2 topic list -t` | +| `getServiceNamesAndTypes()` | `ros2 service list -t` | +| `getPublisherNamesAndTypesByNode()` | `ros2 node info ` | +| `getSubscriptionNamesAndTypesByNode()` | `ros2 node info ` | + +## Advanced Graph Analysis + +### Building Communication Maps + +```javascript +// Create a communication graph +const communicationMap = new Map(); +const nodes = node.getNodeNamesAndNamespaces(); + +for (const nodeData of nodes) { + const pubs = node.getPublisherNamesAndTypesByNode( + nodeData.name, + nodeData.namespace + ); + const subs = node.getSubscriptionNamesAndTypesByNode( + nodeData.name, + nodeData.namespace + ); + + communicationMap.set(`${nodeData.namespace}/${nodeData.name}`, { + publishes: pubs, + subscribes: subs, + }); +} +``` + +### Topic Connectivity Analysis + +```javascript +// Find which nodes communicate via topics +const topics = node.getTopicNamesAndTypes(); +const nodes = node.getNodeNamesAndNamespaces(); + +for (const topic of topics) { + const publishers = []; + const subscribers = []; + + for (const nodeData of nodes) { + const pubs = node.getPublisherNamesAndTypesByNode( + nodeData.name, + nodeData.namespace + ); + const subs = node.getSubscriptionNamesAndTypesByNode( + nodeData.name, + nodeData.namespace + ); + + if (pubs.some((p) => p.name === topic.name)) publishers.push(nodeData); + if (subs.some((s) => s.name === topic.name)) subscribers.push(nodeData); + } + + console.log( + `Topic ${topic.name}: ${publishers.length} publishers, ${subscribers.length} subscribers` + ); +} +``` + +## Notes + +- Graph introspection provides a snapshot of the current system state +- Nodes and communication endpoints can appear/disappear dynamically +- The example creates a static graph for demonstration purposes +- In real systems, you might want to poll these APIs periodically to track changes +- Namespace handling is important for multi-robot or complex systems +- Built-in ROS 2 services and topics are automatically included in discovery results +- Graph introspection is useful for debugging communication issues and system architecture validation diff --git a/example/lifecycle/README.md b/example/lifecycle/README.md new file mode 100644 index 00000000..5e1be36d --- /dev/null +++ b/example/lifecycle/README.md @@ -0,0 +1,397 @@ +# ROS 2 Lifecycle Examples + +This directory contains examples demonstrating ROS 2 lifecycle node functionality using rclnodejs. Lifecycle nodes provide a standardized state machine for managing node behavior and resources throughout their operational lifetime. + +## Overview + +ROS 2 lifecycle nodes implement a well-defined state machine that allows for: + +- **Controlled Startup**: Systematic initialization and configuration +- **Resource Management**: Proper allocation and deallocation of resources +- **State Monitoring**: External supervision and control of node states +- **Graceful Shutdown**: Clean termination and cleanup procedures +- **Error Recovery**: Standardized error handling and recovery mechanisms + +Lifecycle nodes are particularly useful for: + +- System components that require careful initialization +- Nodes that manage hardware resources +- Critical system services that need supervised startup/shutdown +- Applications requiring coordinated state management + +## Lifecycle State Machine + +The ROS 2 lifecycle state machine includes these primary states: + +- **Unconfigured**: Initial state, no resources allocated +- **Inactive**: Configured but not actively processing +- **Active**: Fully operational and processing data +- **Finalized**: Cleaned up and ready for destruction + +State transitions are triggered by these transitions: + +- **configure**: Unconfigured → Inactive +- **activate**: Inactive → Active +- **deactivate**: Active → Inactive +- **shutdown**: Any state → Finalized +- **cleanup**: Inactive → Unconfigured + +## Lifecycle Example + +### Lifecycle Node with Countdown (`lifecycle-node-example.js`) + +**Purpose**: Demonstrates a complete lifecycle node implementation with automated state management. + +#### Functionality + +This example creates a lifecycle node that: + +1. **Countdown Publisher**: Publishes countdown messages from 5 to 0 +2. **Self-Monitoring**: Subscribes to its own messages to monitor progress +3. **Automatic Shutdown**: Deactivates and shuts down when countdown reaches 0 +4. **State Callbacks**: Implements all major lifecycle transition callbacks +5. **Resource Management**: Properly manages publishers, subscribers, and timers + +#### Architecture + +- **Node Name**: `test_node` +- **Topic**: `test` +- **Message Type**: `std_msgs/msg/String` +- **Timer Interval**: 1 second +- **Countdown Range**: 5 down to 0 + +#### State Transition Flow + +1. **Initialize**: Create lifecycle node and register callbacks +2. **Configure**: Create publisher and subscriber +3. **Activate**: Activate publisher and start countdown timer +4. **Monitor**: Watch for countdown completion +5. **Deactivate**: Stop timer and deactivate publisher when countdown reaches 0 +6. **Shutdown**: Clean up resources and exit + +#### Features Demonstrated + +- **Lifecycle Node Creation**: Using `createLifecycleNode()` +- **State Callbacks**: Implementing all major transition callbacks +- **Lifecycle Publisher**: Creating and managing lifecycle-aware publishers +- **State Management**: Manual state transitions (`configure()`, `activate()`, etc.) +- **Resource Cleanup**: Proper timer and publisher management +- **Self-Monitoring**: Node subscribing to its own publications + +#### Run Command + +```bash +node lifecycle-node-example.js +``` + +## Sample Output + +When you run the lifecycle example, you'll see output showing the complete state machine progression: + +### Expected Output + +``` +Lifecycle: CONFIGURE +Lifecycle: ACTIVATE +countdown msg: 5 +countdown msg: 4 +countdown msg: 3 +countdown msg: 2 +countdown msg: 1 +countdown msg: 0 +Lifecycle: DEACTIVATE +Lifecycle: SHUTDOWN +``` + +### Output Explanation + +1. **CONFIGURE**: Node enters inactive state, creates publisher and subscriber +2. **ACTIVATE**: Node enters active state, activates publisher, starts timer +3. **countdown msg: X**: Messages published and received showing countdown progress +4. **DEACTIVATE**: Countdown reaches 0, node deactivates publisher and stops timer +5. **SHUTDOWN**: Node cleans up resources and terminates + +## Lifecycle Node Implementation Details + +### Class Structure + +The example uses a class-based approach with these key components: + +```javascript +class App { + constructor() { + this._node = null; // Lifecycle node instance + this._publisher = null; // Lifecycle publisher + this._subscriber = null; // Regular subscriber + this._timer = null; // Timer for countdown + this._count = COUNTDOWN; // Current countdown value + } +} +``` + +### State Callback Implementation + +#### Configure Callback + +```javascript +onConfigure() { + console.log('Lifecycle: CONFIGURE'); + // Create lifecycle publisher + this._publisher = this._node.createLifecyclePublisher('std_msgs/msg/String', TOPIC); + // Create regular subscriber + this._subscriber = this._node.createSubscription('std_msgs/msg/String', TOPIC, callback); + return rclnodejs.lifecycle.CallbackReturnCode.SUCCESS; +} +``` + +#### Activate Callback + +```javascript +onActivate() { + console.log('Lifecycle: ACTIVATE'); + // Activate the lifecycle publisher + this._publisher.activate(); + // Start the countdown timer + this._timer = this._node.createTimer(BigInt('1000000000'), () => { + this._publisher.publish(`${this._count--}`); + }); + return rclnodejs.lifecycle.CallbackReturnCode.SUCCESS; +} +``` + +#### Deactivate Callback + +```javascript +onDeactivate() { + console.log('Lifecycle: DEACTIVATE'); + // Deactivate publisher + this._publisher.deactivate(); + // Cancel timer + if (this._timer) { + this._timer.cancel(); + this._timer = null; + } + return rclnodejs.lifecycle.CallbackReturnCode.SUCCESS; +} +``` + +#### Shutdown Callback + +```javascript +onShutdown(prevState) { + console.log('Lifecycle: SHUTDOWN'); + // Clean up based on previous state + if (prevState.id === this._StateInterface.PRIMARY_STATE) { + this.onDeactivate(); + this._publisher = null; + this._subscriber = null; + } + return rclnodejs.lifecycle.CallbackReturnCode.SUCCESS; +} +``` + +## Key Lifecycle Concepts + +### Lifecycle Publishers + +Lifecycle publishers are special publishers that: + +- Start in an inactive state when created +- Must be explicitly activated to publish messages +- Can be deactivated to temporarily stop publishing +- Are automatically managed by the lifecycle state machine + +```javascript +// Create lifecycle publisher (inactive by default) +const publisher = node.createLifecyclePublisher('std_msgs/msg/String', 'topic'); + +// Activate publisher (in activate callback) +publisher.activate(); + +// Deactivate publisher (in deactivate callback) +publisher.deactivate(); +``` + +### State Transitions + +Manual state transitions are performed using node methods: + +```javascript +// Trigger state transitions +node.configure(); // Unconfigured → Inactive +node.activate(); // Inactive → Active +node.deactivate(); // Active → Inactive +node.shutdown(); // Any state → Finalized +``` + +### Callback Return Codes + +State callbacks must return appropriate codes: + +- `CallbackReturnCode.SUCCESS`: Transition succeeded +- `CallbackReturnCode.FAILURE`: Transition failed +- `CallbackReturnCode.ERROR`: Error occurred during transition + +## External Lifecycle Management + +You can also control lifecycle nodes externally using ROS 2 services: + +### Lifecycle Service Interface + +Each lifecycle node automatically exposes these services: + +- `/node_name/change_state`: Trigger state transitions +- `/node_name/get_state`: Query current state +- `/node_name/get_available_states`: List available states +- `/node_name/get_available_transitions`: List available transitions + +### Using ROS 2 CLI Tools + +```bash +# Check current state +ros2 lifecycle get /test_node + +# List available transitions +ros2 lifecycle list /test_node + +# Trigger state transitions +ros2 lifecycle set /test_node configure +ros2 lifecycle set /test_node activate +ros2 lifecycle set /test_node deactivate +ros2 lifecycle set /test_node shutdown +``` + +## Advanced Lifecycle Patterns + +### Error Handling + +```javascript +onConfigure() { + try { + // Resource initialization + this._publisher = this._node.createLifecyclePublisher(...); + return rclnodejs.lifecycle.CallbackReturnCode.SUCCESS; + } catch (error) { + console.error('Configuration failed:', error); + return rclnodejs.lifecycle.CallbackReturnCode.FAILURE; + } +} +``` + +### State-Dependent Behavior + +```javascript +onActivate() { + // Only start publishing in active state + this._timer = this._node.createTimer(interval, () => { + if (this._node.getCurrentState().label === 'active') { + this._publisher.publish(data); + } + }); + return rclnodejs.lifecycle.CallbackReturnCode.SUCCESS; +} +``` + +### Resource Monitoring + +```javascript +onDeactivate() { + // Clean up resources + if (this._timer) { + this._timer.cancel(); + this._timer = null; + } + + // Deactivate publishers + if (this._publisher) { + this._publisher.deactivate(); + } + + return rclnodejs.lifecycle.CallbackReturnCode.SUCCESS; +} +``` + +## Use Cases for Lifecycle Nodes + +### Hardware Drivers + +```javascript +onConfigure() { + // Initialize hardware connections + this.initializeHardware(); + return rclnodejs.lifecycle.CallbackReturnCode.SUCCESS; +} + +onActivate() { + // Start hardware communication + this.startHardwareStreaming(); + return rclnodejs.lifecycle.CallbackReturnCode.SUCCESS; +} +``` + +### System Services + +```javascript +onConfigure() { + // Load configuration files + this.loadConfiguration(); + // Setup service endpoints + this.createServices(); + return rclnodejs.lifecycle.CallbackReturnCode.SUCCESS; +} +``` + +### Coordinated Systems + +Multiple lifecycle nodes can be coordinated using external lifecycle managers: + +- System-wide startup sequences +- Graceful shutdown procedures +- Error recovery coordination +- Resource dependency management + +## Troubleshooting + +### Common Issues + +1. **Publisher Not Publishing**: + + - Ensure lifecycle publisher is activated in `onActivate()` + - Check that node is in active state + - Verify publisher is created in `onConfigure()` + +2. **Timer Not Working**: + + - Create timer in `onActivate()`, not `onConfigure()` + - Cancel timer in `onDeactivate()` + - Check timer interval format (BigInt nanoseconds) + +3. **State Transition Failures**: + + - Ensure callbacks return appropriate return codes + - Check for exceptions in callback implementations + - Verify resource cleanup in deactivate/shutdown + +4. **Resource Leaks**: + - Always cancel timers in `onDeactivate()` + - Set references to null in `onShutdown()` + - Properly deactivate lifecycle publishers + +### Debugging Tips + +- Use `ros2 lifecycle get /node_name` to check current state +- Monitor lifecycle transition services for external control +- Add logging to each callback to trace state transitions +- Verify callback return codes are correct +- Check for proper resource cleanup in each state + +## Notes + +- Lifecycle nodes require explicit state management +- Publishers created with `createLifecyclePublisher()` start inactive +- Regular subscribers work normally in lifecycle nodes +- State transitions can be triggered internally or externally +- Proper resource cleanup is critical for lifecycle node reliability +- The example demonstrates self-contained lifecycle management +- External lifecycle managers can coordinate multiple lifecycle nodes +- State callbacks must return appropriate success/failure codes diff --git a/example/parameter/README.md b/example/parameter/README.md new file mode 100644 index 00000000..9de84852 --- /dev/null +++ b/example/parameter/README.md @@ -0,0 +1,316 @@ +# ROS 2 Parameters Examples + +This directory contains examples demonstrating ROS 2 parameter handling using rclnodejs. Parameters provide a way to configure node behavior at runtime and can be modified dynamically during execution. + +## Overview + +ROS 2 parameters are configuration values that can be set on nodes to modify their behavior. Parameters are: + +- **Typed**: Each parameter has a specific type (string, int, double, bool, etc.) +- **Declarable**: Must be declared before use with optional descriptors +- **Dynamic**: Can be changed at runtime through services or command line +- **Discoverable**: Can be listed and inspected by other nodes and tools + +Parameters are ideal for: + +- Configuration settings (rates, thresholds, file paths) +- Behavior modification without code changes +- Runtime tuning and debugging +- Node customization for different environments + +## Parameter Examples + +### 1. Parameter Declaration (`parameter-declaration-example.js`) + +**Purpose**: Demonstrates how to declare and use parameters in a ROS 2 node. + +- **Functionality**: + - Creates a node named `my_node` + - Declares a string parameter `param1` with default value "hello world" + - Uses `ParameterDescriptor` to define parameter metadata + - Checks parameter existence and retrieves its value + - Displays parameter details and descriptor information +- **Features**: + - Parameter declaration with `declareParameter()` + - Parameter existence checking with `hasParameter()` + - Parameter value retrieval with `getParameter()` + - Parameter descriptor access with `getParameterDescriptor()` +- **Parameter Details**: + - **Name**: `param1` + - **Type**: `PARAMETER_STRING` + - **Default Value**: `"hello world"` +- **Run Command**: `node parameter-declaration-example.js` + +### 2. Parameter Override (`parameter-override-example.js`) + +**Purpose**: Shows how to override parameter values using command-line arguments. + +- **Functionality**: + - Creates a node named `my_node` + - Declares a string parameter `param1` with default value "hello world" + - Demonstrates command-line parameter override using ROS args + - Shows how the parameter value is changed from default to override value + - Displays the overridden parameter value and descriptor +- **Features**: + - Command-line parameter override with `--ros-args -p` + - ROS argument parsing integration + - Parameter initialization with custom argv + - Comparison between default and overridden values +- **Parameter Details**: + - **Name**: `param1` + - **Type**: `PARAMETER_STRING` + - **Default Value**: `"hello world"` + - **Override Value**: `"hello ros2"` (via command line) +- **Run Command**: `node parameter-override-example.js` + +## How to Run the Examples + +### Prerequisites + +1. Ensure ROS 2 is installed and sourced +2. Navigate to the `example/parameter` directory + +### Running Parameter Declaration Example + +```bash +cd example/parameter +node parameter-declaration-example.js +``` + +**Expected Output**: + +``` +Declared parameter: param1 +Parameter details: Parameter { + name: 'param1', + type: 4, + value: 'hello world' +} +ParameterDescriptor { + name: 'param1', + type: 4, + description: '', + additional_constraints: '', + read_only: false, + dynamic_typing: false, + floating_point_range: [], + integer_range: [] +} +``` + +### Running Parameter Override Example + +```bash +cd example/parameter +node parameter-override-example.js +``` + +**Expected Output**: + +``` +Declared parameter: param1 +Parameter overridden: Parameter { + name: 'param1', + type: 4, + value: 'hello ros2' +} +ParameterDescriptor { + name: 'param1', + type: 4, + description: '', + additional_constraints: '', + read_only: false, + dynamic_typing: false, + floating_point_range: [], + integer_range: [] +} +``` + +Notice how the value changed from "hello world" to "hello ros2" due to the command-line override. + +## Using ROS 2 Parameter Tools + +You can interact with these examples using standard ROS 2 parameter tools: + +### Listing Parameters + +While either example is running, you can list parameters: + +```bash +ros2 param list +``` + +Expected output: + +``` +/my_node: + param1 + use_sim_time +``` + +### Getting Parameter Values + +```bash +ros2 param get /my_node param1 +``` + +Expected output: + +``` +String value is: hello world +``` + +(or "hello ros2" for the override example) + +### Setting Parameter Values + +```bash +ros2 param set /my_node param1 "new value" +``` + +### Describing Parameters + +```bash +ros2 param describe /my_node param1 +``` + +## Key Concepts Demonstrated + +### Parameter Declaration + +- **Parameter Objects**: Creating `Parameter` instances with name, type, and value +- **Parameter Descriptors**: Using `ParameterDescriptor` for metadata +- **Declaration Process**: Registering parameters with the node using `declareParameter()` +- **Type Safety**: Ensuring parameter types match expected values + +### Parameter Types + +The examples demonstrate `PARAMETER_STRING`, but ROS 2 supports various types: + +- `PARAMETER_BOOL`: Boolean values +- `PARAMETER_INTEGER`: 64-bit signed integers +- `PARAMETER_DOUBLE`: Double-precision floating point +- `PARAMETER_STRING`: UTF-8 encoded strings +- `PARAMETER_BYTE_ARRAY`: Raw byte arrays +- `PARAMETER_BOOL_ARRAY`: Arrays of booleans +- `PARAMETER_INTEGER_ARRAY`: Arrays of integers +- `PARAMETER_DOUBLE_ARRAY`: Arrays of doubles +- `PARAMETER_STRING_ARRAY`: Arrays of strings + +### Parameter Management + +- **Existence Checking**: Using `hasParameter()` to verify parameter presence +- **Value Retrieval**: Getting current parameter values with `getParameter()` +- **Descriptor Access**: Retrieving parameter metadata with `getParameterDescriptor()` +- **Runtime Override**: Modifying parameters via command line or services + +### Command-Line Integration + +- **ROS Args**: Using `--ros-args -p` syntax for parameter overrides +- **Node-Specific Parameters**: Targeting specific nodes with `node_name:param_name:=value` +- **Initialization Integration**: Passing argv to `rclnodejs.init()` for parameter processing + +## Advanced Parameter Usage + +### Parameter Constraints + +ParameterDescriptor can include constraints: + +```javascript +const descriptor = new ParameterDescriptor( + 'my_param', + ParameterType.PARAMETER_INTEGER +); +descriptor.description = 'A sample integer parameter'; +descriptor.additional_constraints = 'Must be positive'; +descriptor.read_only = false; +descriptor.integer_range = [{ from_value: 0, to_value: 100, step: 1 }]; +``` + +### Parameter Callbacks + +You can set up callbacks to respond to parameter changes: + +```javascript +node.setParameterCallback((parameters) => { + for (const param of parameters) { + console.log(`Parameter ${param.name} changed to ${param.value}`); + } +}); +``` + +### Multiple Parameter Types + +```javascript +// String parameter +const stringParam = new Parameter( + 'config_file', + ParameterType.PARAMETER_STRING, + '/path/to/config' +); + +// Integer parameter +const intParam = new Parameter( + 'max_items', + ParameterType.PARAMETER_INTEGER, + 100 +); + +// Boolean parameter +const boolParam = new Parameter( + 'debug_mode', + ParameterType.PARAMETER_BOOL, + false +); + +// Double parameter +const doubleParam = new Parameter( + 'update_rate', + ParameterType.PARAMETER_DOUBLE, + 10.0 +); +``` + +## Troubleshooting + +### Common Issues + +1. **Parameter Not Found**: + + - Ensure parameter is declared before accessing + - Check parameter name spelling + - Verify node has been properly initialized + +2. **Type Mismatch**: + + - Ensure parameter type matches declaration + - Check ParameterType constants are correct + - Verify value type matches parameter type + +3. **Override Not Working**: + + - Check command-line syntax: `--ros-args -p node_name:param_name:=value` + - Ensure node name matches exactly + - Verify rclnodejs.init() is called with argv + +4. **Permission Issues**: + - Check if parameter is marked as read_only + - Verify parameter constraints are satisfied + +### Debugging Tips + +- Use `ros2 param list` to see all available parameters +- Use `ros2 param describe ` to check parameter details +- Use `ros2 param get ` to verify current values +- Check console output for parameter declaration confirmations +- Verify node names match between declaration and command-line usage + +## Notes + +- Parameters must be declared before they can be used or accessed +- Parameter types are enforced and cannot be changed after declaration +- Command-line overrides are processed during `rclnodejs.init()` +- Parameter descriptors provide metadata but are optional +- The `use_sim_time` parameter is automatically added to all nodes +- Parameter names are case-sensitive and must be valid ROS names +- Parameters are node-specific and accessed using the full node path diff --git a/example/rate/README.md b/example/rate/README.md new file mode 100644 index 00000000..31cd5ead --- /dev/null +++ b/example/rate/README.md @@ -0,0 +1,391 @@ +# ROS 2 Rate Examples + +This directory contains examples demonstrating ROS 2 rate control using rclnodejs. Rate control allows you to manage the timing and frequency of operations in ROS 2 applications, providing precise control over loop execution rates. + +## Overview + +ROS 2 rate control provides mechanisms for: + +- **Precise Timing**: Control loop execution frequency with high precision +- **Rate Limiting**: Prevent operations from exceeding desired frequencies +- **Synchronization**: Coordinate timing across different parts of an application +- **Performance Control**: Manage computational load and resource usage +- **Real-time Behavior**: Achieve predictable timing for time-critical applications + +Rate control is essential for: + +- Sensor data processing at specific frequencies +- Control loops requiring precise timing +- Throttling high-frequency operations +- Synchronizing multiple processes +- Managing system resource usage + +## Rate Example + +### Rate-Limited Loop with High-Frequency Publisher (`rate-example.js`) + +**Purpose**: Demonstrates rate control in a scenario with mismatched publication and processing frequencies. + +#### Functionality + +This example creates a sophisticated timing demonstration that shows: + +1. **High-Frequency Publisher**: Publishes messages every 10ms (100 Hz) +2. **Rate-Limited Subscriber**: Processes messages at 0.5 Hz (every 2 seconds) +3. **Message Filtering**: Demonstrates how rate limiting affects message processing +4. **Timing Visualization**: Shows timestamps to illustrate timing differences + +#### Architecture + +- **Node Name**: `test_node` +- **Topic**: `topic` +- **Message Type**: `std_msgs/msg/String` +- **Publisher Rate**: 100 Hz (every 10ms) +- **Processing Rate**: 0.5 Hz (every 2 seconds) +- **Rate Ratio**: 1:200 (subscriber sees every 200th published message) + +#### Key Components + +1. **Publisher**: Uses `setInterval()` for high-frequency publishing +2. **Subscriber**: Receives all messages but processes at controlled rate +3. **Rate Object**: Controls main loop execution frequency +4. **Spin Control**: Processes callbacks at rate-limited intervals + +#### Features Demonstrated + +- **Rate Creation**: Using `node.createRate(frequency)` +- **Rate Sleep**: Blocking execution with `await rate.sleep()` +- **Spin Control**: Using `spinOnce()` for controlled callback processing +- **Frequency Mismatch**: Handling different publish/process rates +- **Timestamp Tracking**: Monitoring message timing and processing delays + +#### Run Command + +```bash +node rate-example.js +``` + +## Sample Output + +When you run the rate example, you'll see output showing the rate-limited message processing: + +### Expected Output Pattern + +``` +Received(1642694400123): hello 1642694400012 +Received(1642694402134): hello 1642694402089 +Received(1642694404145): hello 1642694404123 +Received(1642694406156): hello 1642694406134 +... +``` + +### Output Analysis + +- **Received Timestamp**: When the subscriber processed the message (every ~2 seconds) +- **Message Timestamp**: When the message was originally published +- **Time Gap**: Shows the delay between publication and processing +- **Missing Messages**: Only every ~200th message is processed due to rate limiting + +### Monitoring All Messages + +To see all published messages (not just the rate-limited ones), run: + +```bash +ros2 topic echo topic std_msgs/msg/String +``` + +This will show the high-frequency stream: + +``` +data: hello 1642694400012 +--- +data: hello 1642694400022 +--- +data: hello 1642694400032 +--- +... (continuing every 10ms) +``` + +## Rate Control Implementation Details + +### Rate Object Creation + +```javascript +// Create a rate object for 0.5 Hz (once every 2 seconds) +const rate = await node.createRate(0.5); +``` + +Rate frequencies can be specified as: + +- **Hertz (Hz)**: Cycles per second (e.g., `0.5` = once every 2 seconds) +- **Common Frequencies**: `1.0` (1 Hz), `10.0` (10 Hz), `100.0` (100 Hz) + +### Rate-Limited Loop Pattern + +```javascript +let forever = true; +while (forever) { + await rate.sleep(); // Wait for next rate cycle + rclnodejs.spinOnce(node, 1000); // Process callbacks for up to 1000ms +} +``` + +### Key Rate Control Concepts + +#### Rate Sleep Behavior + +- **Blocking**: `rate.sleep()` blocks execution until the next rate cycle +- **Precise Timing**: Compensates for processing time to maintain exact frequency +- **Async/Await**: Uses modern JavaScript patterns for clean asynchronous code + +#### Spin Control Integration + +- **spinOnce()**: Processes callbacks once with optional timeout +- **Rate Coordination**: Combines with rate limiting for controlled processing +- **Non-blocking**: Allows rate control without blocking other operations + +## Advanced Rate Control Patterns + +### Multiple Rate Objects + +```javascript +// Different rates for different operations +const fastRate = await node.createRate(10.0); // 10 Hz +const slowRate = await node.createRate(1.0); // 1 Hz + +// Use appropriate rate for each operation +while (running) { + if (needsFastProcessing) { + await fastRate.sleep(); + processFastData(); + } else { + await slowRate.sleep(); + processSlowData(); + } + rclnodejs.spinOnce(node); +} +``` + +### Conditional Rate Control + +```javascript +const rate = await node.createRate(baseFrequency); + +while (running) { + await rate.sleep(); + + if (systemLoad < threshold) { + // Process more data when system load is low + processData(); + } + + rclnodejs.spinOnce(node); +} +``` + +### Rate with Timer Integration + +```javascript +// Combine rate control with timers for complex timing +const rate = await node.createRate(1.0); +const timer = node.createTimer(BigInt(100000000), () => { + // High-frequency timer operation + publishSensorData(); +}); + +while (running) { + await rate.sleep(); + // Low-frequency control operation + updateControlParameters(); + rclnodejs.spinOnce(node); +} +``` + +## Rate Control Use Cases + +### Sensor Data Processing + +```javascript +const sensorRate = await node.createRate(50.0); // 50 Hz sensor processing + +while (running) { + await sensorRate.sleep(); + + // Process sensor data at controlled rate + const sensorData = readSensors(); + const processedData = processSensorData(sensorData); + publishProcessedData(processedData); + + rclnodejs.spinOnce(node); +} +``` + +### Control Loops + +```javascript +const controlRate = await node.createRate(100.0); // 100 Hz control loop + +while (running) { + await controlRate.sleep(); + + // Execute control algorithm at precise frequency + const currentState = getCurrentState(); + const controlCommand = computeControl(currentState, setpoint); + sendControlCommand(controlCommand); + + rclnodejs.spinOnce(node); +} +``` + +### Data Throttling + +```javascript +const throttleRate = await node.createRate(2.0); // Throttle to 2 Hz + +const dataBuffer = []; + +// High-frequency data collection +setInterval(() => { + dataBuffer.push(collectData()); +}, 1); // Collect every 1ms + +// Rate-limited data processing +while (running) { + await throttleRate.sleep(); + + if (dataBuffer.length > 0) { + const batch = dataBuffer.splice(0, 100); // Process in batches + processBatch(batch); + } + + rclnodejs.spinOnce(node); +} +``` + +## Performance Considerations + +### Rate Accuracy + +- Rate objects maintain precise timing by compensating for processing delays +- Long processing times may cause rate drift or missed cycles +- Monitor actual execution frequency vs. target frequency + +### Resource Management + +```javascript +// Monitor rate performance +const startTime = Date.now(); +let cycleCount = 0; + +const rate = await node.createRate(10.0); + +while (running) { + await rate.sleep(); + + // Your processing here + processData(); + + cycleCount++; + + // Check actual frequency periodically + if (cycleCount % 100 === 0) { + const elapsed = (Date.now() - startTime) / 1000; + const actualRate = cycleCount / elapsed; + console.log(`Target: 10 Hz, Actual: ${actualRate.toFixed(2)} Hz`); + } + + rclnodejs.spinOnce(node); +} +``` + +### Memory and CPU Usage + +- Rate objects have minimal overhead +- `spinOnce()` timeout prevents indefinite blocking +- Consider processing time relative to rate period + +## Comparing Rate Control Methods + +| Method | Use Case | Precision | Complexity | +| --------------- | -------------------------- | --------- | ---------- | +| `createRate()` | Precise frequency control | High | Low | +| `setInterval()` | Simple periodic operations | Medium | Very Low | +| `createTimer()` | ROS-integrated timing | High | Low | +| `setTimeout()` | One-time delays | Low | Very Low | + +### When to Use Rate Control + +- **High-precision timing requirements** +- **Frequency synchronization across operations** +- **Rate limiting for resource management** +- **Control loop implementation** +- **Real-time system behavior** + +### When to Use Alternatives + +- **Simple periodic publishing**: Use `createTimer()` +- **One-time operations**: Use `setTimeout()` +- **Event-driven processing**: Use callbacks without rate control + +## Troubleshooting + +### Common Issues + +1. **Rate Drift**: + + - Processing time exceeds rate period + - Solution: Optimize processing or reduce rate frequency + - Monitor actual vs. target frequency + +2. **Missed Messages**: + + - Rate limiting causes message drops + - Expected behavior in the example (only every 200th message processed) + - Use `ros2 topic echo` to see all messages + +3. **High CPU Usage**: + + - Rate loop running too fast for processing capacity + - Solution: Reduce rate frequency or optimize processing + - Monitor system resource usage + +4. **Irregular Timing**: + - System load affecting rate precision + - Solution: Use real-time operating system or lower rates + - Consider process priority settings + +### Debugging Rate Control + +```javascript +const rate = await node.createRate(10.0); +let lastTime = Date.now(); + +while (running) { + await rate.sleep(); + + const currentTime = Date.now(); + const interval = currentTime - lastTime; + console.log(`Rate interval: ${interval}ms (target: 100ms)`); + lastTime = currentTime; + + rclnodejs.spinOnce(node); +} +``` + +### Monitoring Rate Performance + +- Use timing logs to verify rate accuracy +- Monitor CPU and memory usage +- Check for rate drift over long periods +- Validate that critical timing requirements are met + +## Notes + +- Rate control provides precise timing for predictable system behavior +- The example demonstrates intentional rate mismatch for educational purposes +- In production, match rates to system requirements and capabilities +- Rate objects automatically compensate for processing delays +- `spinOnce()` timeout prevents indefinite blocking on callback processing +- Rate control is essential for real-time and control applications +- Consider system limitations when choosing target frequencies +- Use monitoring to verify actual vs. target performance diff --git a/example/rosidl/README.md b/example/rosidl/README.md new file mode 100644 index 00000000..1cefc448 --- /dev/null +++ b/example/rosidl/README.md @@ -0,0 +1,519 @@ +# ROS IDL (ROSIDL) Parser Examples + +This directory contains examples demonstrating ROSIDL parsing capabilities using rclnodejs. ROSIDL (ROS Interface Definition Language) is used to define message, service, and action interfaces in ROS 2. These examples show how to programmatically parse and inspect ROS 2 interface definitions. + +## Overview + +ROSIDL parsing provides capabilities for: + +- **Interface Introspection**: Programmatically examine message, service, and action definitions +- **Dynamic Analysis**: Understand interface structures at runtime +- **Code Generation**: Support for automatic code generation tools +- **Validation**: Verify interface compatibility and structure +- **Development Tools**: Build IDEs, debuggers, and analysis tools + +ROSIDL parsing is useful for: + +- Development tools and IDEs +- Dynamic message handling +- Interface validation and testing +- Code generation and automation +- Runtime interface discovery +- Bridge implementations between different systems + +## ROSIDL Examples + +### 1. Message Parsing (`rosidl-parse-msg-example.js`) + +**Purpose**: Demonstrates parsing ROS 2 message definition files (.msg). + +#### Functionality + +This example shows how to: + +- Parse a standard ROS 2 message file +- Extract message name and package information +- Analyze field definitions and their types +- Understand message structure programmatically + +#### Target Message + +- **Package**: `std_msgs` +- **Message**: `ColorRGBA.msg` +- **File Path**: `$AMENT_PREFIX_PATH/share/std_msgs/msg/ColorRGBA.msg` + +#### Message Structure + +The `ColorRGBA` message contains: + +``` +float32 r # Red component +float32 g # Green component +float32 b # Blue component +float32 a # Alpha (transparency) component +``` + +#### Features Demonstrated + +- **Environment Integration**: Uses `AMENT_PREFIX_PATH` to locate ROS packages +- **Message Parsing**: Using `parseMessageFile()` method +- **Field Analysis**: Iterating through message fields +- **Type Information**: Accessing field types and names + +#### Run Command + +```bash +node rosidl-parse-msg-example.js +``` + +#### Expected Output + +``` +msg name: ColorRGBA +fields includes: +{ + name: 'r', + type: 'float32', + isArray: false, + arraySize: 0, + isUpperBound: false, + defaultValue: null +} +{ + name: 'g', + type: 'float32', + isArray: false, + arraySize: 0, + isUpperBound: false, + defaultValue: null +} +{ + name: 'b', + type: 'float32', + isArray: false, + arraySize: 0, + isUpperBound: false, + defaultValue: null +} +{ + name: 'a', + type: 'float32', + isArray: false, + arraySize: 0, + isUpperBound: false, + defaultValue: null +} +``` + +### 2. Service Parsing (`rosidl-parse-srv-example.js`) + +**Purpose**: Demonstrates parsing ROS 2 service definition files (.srv). + +#### Functionality + +This example shows how to: + +- Parse ROS 2 service interface definitions +- Extract service name and package information +- Analyze request and response field structures +- Understand service interface contracts + +#### Target Service + +- **Package**: `std_srvs` +- **Service**: `SetBool.srv` +- **File Path**: `$AMENT_PREFIX_PATH/share/std_srvs/srv/SetBool.srv` + +#### Service Structure + +The `SetBool` service contains: + +``` +# Request +bool data # Input boolean value +--- +# Response +bool success # Indicates success/failure +string message # Informational message +``` + +#### Features Demonstrated + +- **Service Parsing**: Using `parseServiceFile()` method +- **Request/Response Analysis**: Separate handling of request and response parts +- **Package Information**: Extracting package and service names +- **Field Iteration**: Processing both request and response fields + +#### Run Command + +```bash +node rosidl-parse-srv-example.js +``` + +#### Expected Output + +``` +srv name: SetBool +pkg name: std_srvs +srv request fields includes: +{ + name: 'data', + type: 'bool', + isArray: false, + arraySize: 0, + isUpperBound: false, + defaultValue: null +} +srv response fields includes: +{ + name: 'success', + type: 'bool', + isArray: false, + arraySize: 0, + isUpperBound: false, + defaultValue: null +} +{ + name: 'message', + type: 'string', + isArray: false, + arraySize: 0, + isUpperBound: false, + defaultValue: null +} +``` + +### 3. Action Parsing (`rosidl-parse-action-example.js`) + +**Purpose**: Demonstrates parsing ROS 2 action definition files (.action). + +#### Functionality + +This example shows how to: + +- Parse ROS 2 action interface definitions +- Extract action name and package information +- Analyze goal, result, and feedback field structures +- Understand complete action interface specifications + +#### Target Action + +- **Package**: `test_msgs` +- **Action**: `Fibonacci.action` +- **File Path**: `$AMENT_PREFIX_PATH/share/test_msgs/action/Fibonacci.action` + +#### Action Structure + +The `Fibonacci` action contains: + +``` +# Goal +int32 order # Fibonacci sequence order +--- +# Result +int32[] sequence # Complete Fibonacci sequence +--- +# Feedback +int32[] partial_sequence # Partial sequence (feedback) +``` + +#### Features Demonstrated + +- **Action Parsing**: Using `parseActionFile()` method +- **Three-Part Analysis**: Handling goal, result, and feedback sections +- **Array Types**: Processing array field definitions +- **Complex Structures**: Understanding multi-part action interfaces + +#### Run Command + +```bash +node rosidl-parse-action-example.js +``` + +#### Expected Output + +``` +action name: Fibonacci +pkg name: test_msgs +action goal fields includes: +{ + name: 'order', + type: 'int32', + isArray: false, + arraySize: 0, + isUpperBound: false, + defaultValue: null +} +action result fields includes: +{ + name: 'sequence', + type: 'int32', + isArray: true, + arraySize: 0, + isUpperBound: false, + defaultValue: null +} +action feedback fields includes: +{ + name: 'partial_sequence', + type: 'int32', + isArray: true, + arraySize: 0, + isUpperBound: false, + defaultValue: null +} +``` + +## Field Structure Analysis + +### Field Object Properties + +Each parsed field contains these properties: + +- **`name`**: Field name as defined in the interface +- **`type`**: ROS 2 primitive or complex type (e.g., `int32`, `string`, `float64`) +- **`isArray`**: Boolean indicating if field is an array +- **`arraySize`**: Array size (0 for dynamic arrays) +- **`isUpperBound`**: Whether array size is an upper bound +- **`defaultValue`**: Default value if specified (usually null) + +### Common ROS 2 Types + +- **Primitive Types**: `bool`, `int8`, `int16`, `int32`, `int64`, `uint8`, `uint16`, `uint32`, `uint64`, `float32`, `float64`, `string` +- **Array Types**: `int32[]`, `string[]`, `float64[]` +- **Bounded Arrays**: `int32[10]`, `string[5]` +- **Complex Types**: Custom message types from other packages + +## ROSIDL Parser API + +### Message Parsing + +```javascript +const parser = require('../rosidl_parser/rosidl_parser.js'); + +parser.parseMessageFile(packageName, packagePath).then((spec) => { + console.log(`Message: ${spec.msgName}`); + spec.fields.forEach((field) => { + console.log(`Field: ${field.name} (${field.type})`); + }); +}); +``` + +### Service Parsing + +```javascript +parser.parseServiceFile(packageName, packagePath).then((spec) => { + console.log(`Service: ${spec.srvName}`); + console.log('Request fields:', spec.request.fields); + console.log('Response fields:', spec.response.fields); +}); +``` + +### Action Parsing + +```javascript +parser.parseActionFile(packageName, packagePath).then((spec) => { + console.log(`Action: ${spec.actionName}`); + console.log('Goal fields:', spec.goal.fields); + console.log('Result fields:', spec.result.fields); + console.log('Feedback fields:', spec.feedback.fields); +}); +``` + +## Environment Setup + +### Prerequisites + +1. **ROS 2 Installation**: Properly installed and sourced ROS 2 environment +2. **AMENT_PREFIX_PATH**: Environment variable pointing to ROS 2 package locations +3. **Package Availability**: Target packages (`std_msgs`, `std_srvs`, `test_msgs`) must be installed + +### Verification + +Check your environment setup: + +```bash +echo $AMENT_PREFIX_PATH +ls $AMENT_PREFIX_PATH/share/std_msgs/msg/ +ls $AMENT_PREFIX_PATH/share/std_srvs/srv/ +ls $AMENT_PREFIX_PATH/share/test_msgs/action/ +``` + +## Advanced Usage Patterns + +### Dynamic Interface Discovery + +```javascript +const fs = require('fs'); +const path = require('path'); + +// Find all message files in a package +function findMessageFiles(packagePath) { + const msgDir = path.join(packagePath, 'msg'); + if (fs.existsSync(msgDir)) { + return fs + .readdirSync(msgDir) + .filter((file) => file.endsWith('.msg')) + .map((file) => path.join(msgDir, file)); + } + return []; +} + +// Parse all messages in a package +async function parseAllMessages(packageName) { + const packagePath = path.join( + process.env.AMENT_PREFIX_PATH, + 'share', + packageName + ); + const msgFiles = findMessageFiles(packagePath); + + for (const msgFile of msgFiles) { + try { + const spec = await parser.parseMessageFile(packageName, msgFile); + console.log(`Parsed: ${spec.msgName}`); + } catch (error) { + console.error(`Failed to parse ${msgFile}:`, error); + } + } +} +``` + +### Interface Validation + +```javascript +function validateMessageStructure(spec, expectedFields) { + const actualFields = spec.fields.map((f) => ({ name: f.name, type: f.type })); + + for (const expected of expectedFields) { + const actual = actualFields.find((f) => f.name === expected.name); + if (!actual) { + throw new Error(`Missing field: ${expected.name}`); + } + if (actual.type !== expected.type) { + throw new Error( + `Type mismatch for ${expected.name}: expected ${expected.type}, got ${actual.type}` + ); + } + } + + return true; +} + +// Usage +parser.parseMessageFile('std_msgs', messagePath).then((spec) => { + const expectedFields = [ + { name: 'r', type: 'float32' }, + { name: 'g', type: 'float32' }, + { name: 'b', type: 'float32' }, + { name: 'a', type: 'float32' }, + ]; + validateMessageStructure(spec, expectedFields); + console.log('Message structure is valid'); +}); +``` + +### Code Generation Helper + +```javascript +function generateFieldAccessors(spec) { + const accessors = []; + + for (const field of spec.fields) { + const getter = `get${field.name.charAt(0).toUpperCase() + field.name.slice(1)}() { return this.${field.name}; }`; + const setter = `set${field.name.charAt(0).toUpperCase() + field.name.slice(1)}(value) { this.${field.name} = value; }`; + accessors.push(getter, setter); + } + + return accessors.join('\n'); +} + +// Generate accessors for a message +parser.parseMessageFile('std_msgs', colorRGBAPath).then((spec) => { + const accessors = generateFieldAccessors(spec); + console.log('Generated accessors:\n', accessors); +}); +``` + +## Use Cases for ROSIDL Parsing + +### Development Tools + +- **IDE Integration**: Provide autocomplete and syntax highlighting +- **Interface Browsers**: Build tools to explore available interfaces +- **Documentation Generators**: Automatically generate interface documentation +- **Type Checkers**: Validate message usage in code + +### Runtime Applications + +- **Dynamic Message Handling**: Process messages with unknown structures +- **Bridge Implementations**: Convert between different message formats +- **Protocol Analyzers**: Inspect and analyze ROS 2 communication +- **Testing Frameworks**: Generate test data based on interface definitions + +### Code Generation + +- **Binding Generators**: Create language bindings for different platforms +- **Serialization Code**: Generate efficient serialization/deserialization +- **Mock Generators**: Create mock implementations for testing +- **Documentation**: Automatically generate API documentation + +## Troubleshooting + +### Common Issues + +1. **AMENT_PREFIX_PATH Not Set**: + + ``` + Error: Cannot read property 'AMENT_PREFIX_PATH' of undefined + ``` + + Solution: Source your ROS 2 setup file: + + ```bash + source /opt/ros/humble/setup.bash + ``` + +2. **Package Not Found**: + + ``` + Error: ENOENT: no such file or directory + ``` + + Solution: Verify package installation: + + ```bash + ros2 pkg list | grep std_msgs + ``` + +3. **Permission Issues**: + + ``` + Error: EACCES: permission denied + ``` + + Solution: Check file permissions and ROS 2 installation + +4. **Parsing Errors**: + ``` + Error: Failed to parse interface file + ``` + Solution: Verify interface file syntax and structure + +### Debugging Tips + +- Verify `AMENT_PREFIX_PATH` contains your ROS 2 installation +- Check that target packages are properly installed +- Use absolute paths for debugging file location issues +- Test with known-good interface files first +- Check ROS 2 installation completeness + +## Notes + +- ROSIDL parsing requires a properly configured ROS 2 environment +- Examples use standard ROS 2 packages that should be available in most installations +- Parser results provide complete interface structure information +- Field properties include array information and type details +- The parser handles both primitive and complex types +- Service and action parsing provide separate access to different interface sections +- Results can be used for code generation, validation, and dynamic message handling diff --git a/example/services/README.md b/example/services/README.md new file mode 100644 index 00000000..ed72f7cb --- /dev/null +++ b/example/services/README.md @@ -0,0 +1,179 @@ +# ROS 2 Services Examples + +This directory contains examples demonstrating ROS 2 service-based communication using rclnodejs. These examples showcase client-server patterns for request-response communication in ROS 2. + +## Overview + +ROS 2 services provide a request-response communication pattern where clients send requests to services, and services process these requests and send back responses. This is different from topics which use a publish-subscribe pattern. Services are ideal for: + +- Remote procedure calls +- Getting/setting configuration parameters +- Triggering actions that require confirmation +- Any communication that needs a response + +## Service Examples + +### Service Server (`service/service-example.js`) + +**Purpose**: Demonstrates creating a service server that adds two integers. + +- **Service Type**: `example_interfaces/srv/AddTwoInts` +- **Service Name**: `add_two_ints` +- **Functionality**: + - Receives requests with two BigInt numbers (`a` and `b`) + - Computes the sum (`a + b`) + - Sends back the result in the response + - Logs incoming requests and outgoing responses +- **Features**: + - Service introspection (ROS 2 Iron+) for monitoring service calls + - Proper response handling using `response.template` and `response.send()` +- **Run Command**: `node service/service-example.js` + +### Service Client (`client/client-example.js`) + +**Purpose**: Demonstrates creating a service client that sends requests to the AddTwoInts service. + +- **Service Type**: `example_interfaces/srv/AddTwoInts` +- **Service Name**: `add_two_ints` +- **Functionality**: + - Generates two random BigInt numbers (0-99) + - Sends a request to the service with these numbers + - Waits for and logs the response + - Automatically shuts down after receiving the response +- **Features**: + - Service availability checking with `waitForService()` + - Service introspection configuration (ROS 2 Iron+) + - Asynchronous request handling with callbacks +- **Run Command**: `node client/client-example.js` + +## How to Run the Examples + +### Running the Complete Service Example + +1. **Prerequisites**: Ensure ROS 2 is installed and sourced + +2. **Start the Service Server**: In one terminal, run: + + ```bash + cd example/services + node service/service-example.js + ``` + + You should see: + + ``` + Introspection configured + ``` + +3. **Start the Client**: In another terminal, run: + + ```bash + cd example/services + node client/client-example.js + ``` + +4. **Expected Output**: + + **Service Server Terminal**: + + ``` + Incoming request: object { a: 42n, b: 37n } + Sending response: object { sum: 79n } + -- + ``` + + **Client Terminal**: + + ``` + Sending: object { a: 42n, b: 37n } + Result: object { sum: 79n } + ``` + +### Single Run vs Continuous Operation + +- **Client**: Runs once, sends a request, receives response, then shuts down +- **Service**: Runs continuously, waiting for requests until manually terminated (Ctrl+C) + +## Key Concepts Demonstrated + +### Service Communication Pattern + +- **Request-Response**: Synchronous communication where clients wait for responses +- **Service Discovery**: Clients check if services are available before sending requests +- **Error Handling**: Proper handling of service unavailability + +### ROS 2 Service Features + +- **BigInt Support**: Using JavaScript BigInt for ROS 2 integer types +- **Service Introspection**: Monitoring service calls and events (ROS 2 Iron+) +- **Quality of Service**: Configurable QoS profiles for reliable communication + +### Programming Patterns + +- **Async/Await**: Modern JavaScript patterns for asynchronous operations +- **Callback Handling**: Response processing using callback functions +- **Resource Management**: Proper node shutdown and cleanup + +## Service Introspection (ROS 2 Iron+) + +Both examples include service introspection capabilities for ROS 2 distributions newer than Humble: + +- **Service Server**: Configured with `CONTENTS` introspection to log full request/response data +- **Client**: Configured with `METADATA` introspection to log service call metadata + +To monitor service events, use: + +```bash +ros2 topic echo "/add_two_ints/_service_event" +``` + +## Message Types and Data Handling + +### AddTwoInts Service Definition + +``` +# Request +int64 a +int64 b +--- +# Response +int64 sum +``` + +### JavaScript Implementation Details + +- **BigInt Usage**: ROS 2 `int64` maps to JavaScript `BigInt` type +- **Response Template**: Use `response.template` to get the proper response structure +- **Response Sending**: Call `response.send(result)` to send the response back + +## Troubleshooting + +### Common Issues + +1. **Service Not Available**: + + - Ensure the service server is running before starting the client + - Check that both use the same service name (`add_two_ints`) + +2. **Type Errors**: + + - Ensure you're using `BigInt()` for integer values, not regular numbers + - Use `response.template` to get the correct response structure + +3. **Client Hangs**: + - The client waits for service availability with a 1-second timeout + - If the service isn't available, the client will log an error and shut down + +### Debugging Tips + +- Use `ros2 service list` to see available services +- Use `ros2 service type ` to check service types +- Use `ros2 service call ` to test services from command line + +## Notes + +- Both examples use the standard rclnodejs initialization pattern +- The service server runs continuously until manually terminated +- The client performs a single request-response cycle then exits +- Service introspection is only available in ROS 2 Iron and later distributions +- BigInt is required for integer message fields to maintain precision diff --git a/example/topics/README.md b/example/topics/README.md new file mode 100644 index 00000000..7a530cb3 --- /dev/null +++ b/example/topics/README.md @@ -0,0 +1,204 @@ +# ROS 2 Topics Examples + +This directory contains examples demonstrating ROS 2 topic-based communication using rclnodejs. These examples showcase publishers, subscribers, and various messaging patterns available in ROS 2. + +## Overview + +ROS 2 topics are a fundamental communication pattern that allows nodes to exchange messages in a publish-subscribe manner. Publishers send messages to topics, and subscribers receive messages from topics they're interested in. + +## Publisher Examples + +The `publisher/` directory contains examples of nodes that publish messages to topics: + +### 1. Basic Publisher (`publisher-example.js`) + +**Purpose**: Demonstrates basic string message publishing. + +- **Message Type**: `std_msgs/msg/String` +- **Topic**: `topic` +- **Functionality**: Publishes "Hello ROS" messages every second +- **Run Command**: `node publisher/publisher-example.js` + +### 2. Content Filter Publisher (`publisher-content-filter-example.js`) + +**Purpose**: Publishes temperature data for content filtering demonstrations. + +- **Message Type**: `sensor_msgs/msg/Temperature` +- **Topic**: `temperature` +- **Functionality**: Publishes random temperature values (0-100°C) every 100ms with header information +- **Run Command**: `node publisher/publisher-content-filter-example.js` +- **Pair**: Works with `subscription-content-filter-example.js` + +### 3. Message Publisher (`publisher-message-example.js`) + +**Purpose**: Demonstrates publishing complex structured messages. + +- **Message Type**: `sensor_msgs/msg/JointState` +- **Topic**: `JointState` +- **Functionality**: Publishes joint state information with header, names, positions, velocities, and efforts +- **Run Command**: `node publisher/publisher-message-example.js` +- **Pair**: Works with `subscription-message-example.js` + +### 4. MultiArray Publisher (`publisher-multiarray-example.js`) + +**Purpose**: Shows how to publish multi-dimensional array data. + +- **Message Type**: `std_msgs/msg/Int32MultiArray` +- **Topic**: `Int32MultiArray` +- **Functionality**: Publishes 3D array data (2×3×3) with proper layout information +- **Run Command**: `node publisher/publisher-multiarray-example.js` +- **Pair**: Works with `subscription-multiarray-example.js` + +### 5. QoS Publisher (`publisher-qos-example.js`) + +**Purpose**: Demonstrates Quality of Service (QoS) configuration for publishers. + +- **Message Type**: `std_msgs/msg/String` +- **Topic**: `topic` +- **Functionality**: Publishes messages with custom QoS settings (system default policies) +- **Run Command**: `node publisher/publisher-qos-example.js` +- **Pair**: Works with `subscription-qos-example.js` + +### 6. Raw Message Publisher (`publisher-raw-message.js`) + +**Purpose**: Shows how to publish raw binary data. + +- **Message Type**: `test_msgs/msg/BasicTypes` +- **Topic**: `chatter` +- **Functionality**: Publishes raw Buffer data ("Hello ROS World") +- **Run Command**: `node publisher/publisher-raw-message.js` +- **Pair**: Works with `subscription-raw-message.js` + +## Subscriber Examples + +The `subscriber/` directory contains examples of nodes that subscribe to topics: + +### 1. Basic Subscriber (`subscription-example.js`) + +**Purpose**: Demonstrates basic message subscription. + +- **Message Type**: `std_msgs/msg/String` +- **Topic**: `topic` +- **Functionality**: Receives and logs string messages +- **Run Command**: `node subscriber/subscription-example.js` +- **Pair**: Works with `publisher-example.js` + +### 2. Content Filter Subscriber (`subscription-content-filter-example.js`) + +**Purpose**: Demonstrates content filtering to receive only relevant messages. + +- **Message Type**: `sensor_msgs/msg/Temperature` +- **Topic**: `temperature` +- **Functionality**: Only receives temperature messages above 50°C using content filters +- **Features**: ROS 2 Humble+ content filtering with expression `temperature > %0` +- **Run Command**: `node subscriber/subscription-content-filter-example.js` +- **Pair**: Works with `publisher-content-filter-example.js` + +### 3. Message Subscriber (`subscription-message-example.js`) + +**Purpose**: Receives complex structured messages. + +- **Message Type**: `sensor_msgs/msg/JointState` +- **Topic**: `JointState` +- **Functionality**: Receives and logs joint state information +- **Run Command**: `node subscriber/subscription-message-example.js` +- **Pair**: Works with `publisher-message-example.js` + +### 4. MultiArray Subscriber (`subscription-multiarray-example.js`) + +**Purpose**: Demonstrates receiving and parsing multi-dimensional arrays. + +- **Message Type**: `std_msgs/msg/Int32MultiArray` +- **Topic**: `Int32MultiArray` +- **Functionality**: Receives 3D arrays and iterates through all elements with proper indexing +- **Features**: Shows how to parse layout information and access array elements +- **Run Command**: `node subscriber/subscription-multiarray-example.js` +- **Pair**: Works with `publisher-multiarray-example.js` + +### 5. QoS Subscriber (`subscription-qos-example.js`) + +**Purpose**: Demonstrates QoS configuration for subscribers. + +- **Message Type**: `std_msgs/msg/String` +- **Topic**: `topic` +- **Functionality**: Receives messages with system default QoS profile +- **Run Command**: `node subscriber/subscription-qos-example.js` +- **Pair**: Works with `publisher-qos-example.js` + +### 6. Raw Message Subscriber (`subscription-raw-message.js`) + +**Purpose**: Shows how to receive raw binary data. + +- **Message Type**: `test_msgs/msg/BasicTypes` +- **Topic**: `chatter` +- **Functionality**: Receives raw Buffer data and converts to UTF-8 string +- **Features**: Uses `{ isRaw: true }` option +- **Run Command**: `node subscriber/subscription-raw-message.js` +- **Pair**: Works with `publisher-raw-message.js` + +### 7. Service Event Subscriber (`subscription-service-event-example.js`) + +**Purpose**: Demonstrates subscribing to service events. + +- **Message Type**: `example_interfaces/srv/AddTwoInts_Event` +- **Topic**: `/add_two_ints/_service_event` +- **Functionality**: Monitors service call events for the AddTwoInts service +- **Features**: ROS 2 service introspection capabilities +- **Run Command**: `node subscriber/subscription-service-event-example.js` + +## Validator Example + +The `validator/` directory contains validation utilities: + +### Validator (`validator-example.js`) + +**Purpose**: Demonstrates ROS 2 name validation functions. + +- **Functionality**: Validates topic names, node names, namespaces, and full topic names +- **Features**: Uses rclnodejs validator utilities +- **Run Command**: `node validator/validator-example.js` + +## Paired Examples + +Several examples work together to demonstrate complete communication: + +| Publisher | Subscriber | Description | +| ------------------------------------- | ---------------------------------------- | ------------------------------- | +| `publisher-example.js` | `subscription-example.js` | Basic string messaging | +| `publisher-content-filter-example.js` | `subscription-content-filter-example.js` | Temperature data with filtering | +| `publisher-message-example.js` | `subscription-message-example.js` | Complex structured messages | +| `publisher-multiarray-example.js` | `subscription-multiarray-example.js` | Multi-dimensional array data | +| `publisher-qos-example.js` | `subscription-qos-example.js` | QoS configuration | +| `publisher-raw-message.js` | `subscription-raw-message.js` | Raw binary data | + +## How to Run Examples + +1. **Prerequisites**: Ensure ROS 2 is installed and sourced +2. **Navigate**: Change to the example/topics directory +3. **Run Publisher**: Start the publisher in one terminal + ```bash + node publisher/publisher-example.js + ``` +4. **Run Subscriber**: Start the corresponding subscriber in another terminal + ```bash + node subscriber/subscription-example.js + ``` + +## Key Concepts Demonstrated + +- **Basic Pub/Sub**: Simple message exchange patterns +- **Message Types**: Various ROS 2 message types (String, Temperature, JointState, MultiArray) +- **QoS Configuration**: Quality of Service settings for reliable communication +- **Content Filtering**: Selective message reception based on content (ROS 2 Humble+) +- **Raw Messages**: Binary data transmission +- **Service Events**: Monitoring service interactions +- **Multi-dimensional Arrays**: Complex data structures with layout information +- **Validation**: Name and topic validation utilities + +## Notes + +- All examples use the standard rclnodejs initialization pattern +- Most examples run continuously until terminated (Ctrl+C) +- Content filtering requires ROS 2 Humble or later +- Raw message examples require matching message types between publisher and subscriber +- Service event monitoring works with any service but requires the service to be active