Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 4 additions & 11 deletions CONTRIBUTORS.md
Original file line number Diff line number Diff line change
@@ -1,62 +1,55 @@
# rclnodejs contributors (sorted alphabetically)

- **[Alaa El Jawad](https://github.com/ejalaa12)**

- Fix compatibility with ROS2 parameters array types
- Unit tests for all parameter types
- Handle concurrent ROS2 client calls, with unit tests

- **[Alex Mikhalev](https://github.com/amikhalev)**

- Fix build for AMENT_PREFIX_PATH with multiple entries

- **[Felix Divo](https://github.com/felixdivo)**

- Code cleanup of index.js, tests cases & message generation
- Improved shutdown behavior
- Fixed compilation warnings

- **[Hanyia](https://github.com/hanyia)**

- Benchmark test script

- **[Ian McElroy](https://github.com/imcelroy)**

- Add descriptor namespace for all interfaces, rostsd_gen improvements
- Fix compatibility with ROS2 parameters array types
- Unit tests for all parameter types
- Handle concurrent ROS2 client calls, with unit tests

- **[Kenny Yuan](https://github.com/kenny-y)**

- Message features: JS generation, typed arrays, plain JS object, compound msgs, many others...
- npm publish scripts
- Mac support

- **[Matt Richard](https://github.com/mattrichard)**

- ROS2 Actions
- Guard conditions
- Node utility methods (countPublishers/subscribers...)
- TypeScript improvements
- Node 12 compatibility

- **[Minggang Wang](https://github.com/minggangw)**

- Author, lead developer, maintainer
- Core, CI

- **[Martins Mozeiko](https://github.com/martins-mozeiko)**
- **[Mahmoud Alghalayini](https://github.com/mahmoud-ghalayini)**
- JSON safe serialization improvements
- Promise-based service calls implementation

- **[Martins Mozeiko](https://github.com/martins-mozeiko)**
- QoS new/delete fix

- **[Qiuzhong](https://github.com/qiuzhong)**

- Test coverage for actions, topics, multi-array messages, cross platform, security
- Converted from setTimeout to ROS2 Timer

- **[Teo Koon Peng](https://github.com/koonpeng)**

- TypeScript improvements
- Added Client#waitForService
- Code style improvements, e.g., Prettier formatting
Expand Down
114 changes: 109 additions & 5 deletions example/services/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,63 @@ ROS 2 services provide a request-response communication pattern where clients se
- Asynchronous request handling with callbacks
- **Run Command**: `node example/services/client/client-example.js`

#### Async Service Client (`client/async-client-example.js`)

**Purpose**: Demonstrates modern async/await patterns for service communication, solving callback hell and providing cleaner error handling.

- **Service Type**: `example_interfaces/srv/AddTwoInts`
- **Service Name**: `add_two_ints`
- **Functionality**:
- Multiple examples showing different async patterns
- Simple async/await calls without callbacks
- Timeout handling with configurable timeouts
- Request cancellation using AbortController
- Sequential and parallel service calls
- Comprehensive error handling
- **Features**:
- **Modern JavaScript**: Clean async/await syntax instead of callback hell
- **Timeout Support**: Built-in timeout with `options.timeout` (uses `AbortSignal.timeout()` internally)
- **Cancellation**: Request cancellation using `AbortController` and `options.signal`
- **Error Types**: Specific error types (`TimeoutError`, `AbortError`) for better error handling (async only)
- **Backward Compatible**: Works alongside existing callback-based `sendRequest()`
- **TypeScript Ready**: Full type safety with comprehensive TypeScript definitions
- **Run Command**: `node example/services/client/async-client-example.js`

**Key API Differences**:

```javascript
client.sendRequest(request, (response) => {
console.log('Response:', response.sum);
});

try {
const response = await client.sendRequestAsync(request);

const response = await client.sendRequestAsync(request, { timeout: 5000 });

const controller = new AbortController();
const response = await client.sendRequestAsync(request, {
signal: controller.signal,
});

const controller = new AbortController();
const response = await client.sendRequestAsync(request, {
timeout: 5000,
signal: controller.signal,
});

console.log('Response:', response.sum);
} catch (error) {
if (error.name === 'TimeoutError') {
console.log('Request timed out');
} else if (error.name === 'AbortError') {
console.log('Request was cancelled');
} else {
console.error('Service error:', error.message);
}
}
```

### GetMap Service

#### Service Server (`service/getmap-service-example.js`)
Expand Down Expand Up @@ -129,6 +186,41 @@ ROS 2 services provide a request-response communication pattern where clients se
Result: object { sum: 79n }
```

### Running the Async AddTwoInts Client Example

1. **Prerequisites**: Ensure ROS 2 is installed and sourced

2. **Start the Service Server**: Use the same service server as above:

```bash
cd /path/to/rclnodejs
node example/services/service/service-example.js
```

3. **Start the Async Client**: In another terminal, run:

```bash
cd /path/to/rclnodejs
node example/services/client/async-client-example.js
```

4. **Expected Output**:

**Service Server Terminal**: (Same as regular client)

```
Incoming request: object { a: 42n, b: 37n }
Sending response: object { sum: 79n }
--
```

**Async Client Terminal**:

```
Sending: object { a: 42n, b: 37n }
Result: object { sum: 79n }
```

### Running the GetMap Service Example

1. **Prerequisites**: Ensure ROS 2 is installed and sourced
Expand Down Expand Up @@ -236,8 +328,11 @@ This script automatically starts the service, tests the client, and cleans up.

### Programming Patterns

- **Async/Await**: Modern JavaScript patterns for asynchronous operations
- **Callback Handling**: Response processing using callback functions
- **Modern Async/Await**: Clean Promise-based service calls with `sendRequestAsync()`
- **Traditional Callbacks**: Response processing using callback functions with `sendRequest()`
- **Error Handling**: Proper error handling with try/catch blocks and specific error types (async only)
- **Timeout Management**: Built-in timeout support to prevent hanging requests (async only)
- **Request Cancellation**: AbortController support for user-cancellable operations (async only)
- **Resource Management**: Proper node shutdown and cleanup
- **Data Analysis**: Processing and interpreting received data
- **Visualization**: Converting data to human-readable formats
Expand Down Expand Up @@ -331,18 +426,23 @@ int8[] data
### 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
- For async clients, use timeout options: `client.sendRequestAsync(request, { timeout: 5000 })`

4. **Async/Await Issues** (applies only to `sendRequestAsync()`):
- **Unhandled Promise Rejections**: Always use try/catch blocks around `sendRequestAsync()`
- **Timeout Errors**: Handle `TimeoutError` specifically for timeout scenarios (async only)
- **Cancelled Requests**: Handle `AbortError` when using AbortController cancellation (async only)
- **Mixed Patterns**: You can use both `sendRequest()` and `sendRequestAsync()` in the same code

### Debugging Tips

Expand All @@ -354,6 +454,10 @@ int8[] data

- 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
- The traditional client performs a single request-response cycle then exits
- The async client demonstrates multiple patterns and then exits
- **New async/await support**: Use `sendRequestAsync()` for modern Promise-based patterns
- **Full backward compatibility**: Existing `sendRequest()` callback-based code continues to work unchanged
- **TypeScript support**: Full type safety available for async methods
- Service introspection is only available in ROS 2 Iron and later distributions
- BigInt is required for integer message fields to maintain precision
66 changes: 66 additions & 0 deletions example/services/client/async-client-example.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
// Copyright (c) 2025 Mahmoud Alghalayini. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

'use strict';

const rclnodejs = require('../../../index.js');

async function main() {
await rclnodejs.init();
const node = rclnodejs.createNode('async_client_example_node');
const client = node.createClient(
'example_interfaces/srv/AddTwoInts',
'add_two_ints'
);

if (
rclnodejs.DistroUtils.getDistroId() >
rclnodejs.DistroUtils.getDistroId('humble')
) {
// To view service events use the following command:
// ros2 topic echo "/add_two_ints/_service_event"
client.configureIntrospection(
node.getClock(),
rclnodejs.QoS.profileSystemDefault,
rclnodejs.ServiceIntrospectionStates.METADATA
);
}

const request = {
a: BigInt(Math.floor(Math.random() * 100)),
b: BigInt(Math.floor(Math.random() * 100)),
};

let result = await client.waitForService(1000);
if (!result) {
console.log('Error: service not available');
rclnodejs.shutdown();
return;
}

rclnodejs.spin(node);

console.log(`Sending: ${typeof request}`, request);

try {
const response = await client.sendRequestAsync(request, { timeout: 5000 });
console.log(`Result: ${typeof response}`, response);
} catch (error) {
console.log(`Error: ${error.message}`);
} finally {
rclnodejs.shutdown();
}
}

main();
Loading
Loading