Skip to content

Commit 79ac1b0

Browse files
authored
[Kilted] Support to configure introspection for action client/server (#1128)
This PR adds support for action introspection by introducing configuration functions and tests in both the action client and server modules, as well as corresponding C++ bindings. - Added utility functions and tests to manage configuration of introspection for action clients and servers. - Introduced new C++ functions guarded by ROS_VERSION checks to support introspection configuration. - Updated both JavaScript and TypeScript test files to verify the new introspection functionality. Fix: #1129
1 parent c44a0a5 commit 79ac1b0

File tree

12 files changed

+218
-2
lines changed

12 files changed

+218
-2
lines changed

lib/action/client.js

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ const ActionInterfaces = require('./interfaces.js');
1919
const ActionUuid = require('./uuid.js');
2020
const ClientGoalHandle = require('./client_goal_handle.js');
2121
const Deferred = require('./deferred.js');
22+
const DistroUtils = require('../distro.js');
2223
const Entity = require('../entity.js');
2324
const loader = require('../interface_loader.js');
2425
const QoS = require('../qos.js');
@@ -384,6 +385,32 @@ class ActionClient extends Entity {
384385
getNumEntities() {
385386
return rclnodejs.getNumEntities(this.handle);
386387
}
388+
389+
/**
390+
* Configure introspection.
391+
* @param {Clock} clock - Clock to use for service event timestamps
392+
* @param {QoS} qos - QoSProfile for the service event publisher
393+
* @param {ServiceIntrospectionState} introspectionState - State to set introspection to
394+
*/
395+
configureIntrospection(clock, qos, introspectionState) {
396+
if (DistroUtils.getDistroId() <= DistroUtils.getDistroId('jazzy')) {
397+
console.warn(
398+
'Configure action client introspection is not supported by this version of ROS 2'
399+
);
400+
return;
401+
}
402+
403+
let type = this.typeClass.type();
404+
rclnodejs.configureActionClientIntrospection(
405+
this.handle,
406+
this._node.handle,
407+
clock.handle,
408+
type.interfaceName,
409+
type.pkgName,
410+
qos,
411+
introspectionState
412+
);
413+
}
387414
}
388415

389416
module.exports = ActionClient;

lib/action/server.js

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
const rclnodejs = require('bindings')('rclnodejs');
1818
const ActionInterfaces = require('./interfaces.js');
1919
const ActionUuid = require('./uuid.js');
20+
const DistroUtils = require('../distro.js');
2021
const Entity = require('../entity.js');
2122
const { CancelResponse, GoalEvent, GoalResponse } = require('./response.js');
2223
const loader = require('../interface_loader.js');
@@ -454,6 +455,26 @@ class ActionServer extends Entity {
454455

455456
this._node._destroyEntity(this, this._node._actionServers);
456457
}
458+
459+
configureIntrospection(clock, qos, introspectionState) {
460+
if (DistroUtils.getDistroId() <= DistroUtils.getDistroId('jazzy')) {
461+
console.warn(
462+
'Configure action server introspection is not supported by this version of ROS 2'
463+
);
464+
return;
465+
}
466+
467+
let type = this.typeClass.type();
468+
rclnodejs.configureActionServerIntrospection(
469+
this.handle,
470+
this._node.handle,
471+
clock.handle,
472+
type.interfaceName,
473+
type.pkgName,
474+
qos,
475+
introspectionState
476+
);
477+
}
457478
}
458479

459480
module.exports = ActionServer;

lib/client.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,7 @@ class Client extends Entity {
141141
configureIntrospection(clock, qos, introspectionState) {
142142
if (DistroUtils.getDistroId() <= DistroUtils.getDistroId('humble')) {
143143
console.warn(
144-
'Service introspection is not supported by this versionof ROS 2'
144+
'Service introspection is not supported by this version of ROS 2'
145145
);
146146
return;
147147
}

lib/service.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,7 @@ class Service extends Entity {
125125
configureIntrospection(clock, qos, introspectionState) {
126126
if (DistroUtils.getDistroId() <= DistroUtils.getDistroId('humble')) {
127127
console.warn(
128-
'Service introspection is not supported by this versionof ROS 2'
128+
'Service introspection is not supported by this version of ROS 2'
129129
);
130130
return;
131131
}

src/rcl_action_client_bindings.cpp

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
#include <rcl/error_handling.h>
1818
#include <rcl/rcl.h>
19+
#include <rcl_action/action_client.h>
1920
#include <rcl_action/rcl_action.h>
2021

2122
#include <string>
@@ -241,6 +242,45 @@ Napi::Value ActionSendCancelRequest(const Napi::CallbackInfo& info) {
241242
return Napi::Number::New(env, static_cast<int32_t>(sequence_number));
242243
}
243244

245+
#if ROS_VERSION >= 2505 // ROS2 >= Kilted
246+
Napi::Value ConfigureActionClientIntrospection(const Napi::CallbackInfo& info) {
247+
Napi::Env env = info.Env();
248+
RclHandle* action_client_handle =
249+
RclHandle::Unwrap(info[0].As<Napi::Object>());
250+
rcl_action_client_t* action_client =
251+
reinterpret_cast<rcl_action_client_t*>(action_client_handle->ptr());
252+
RclHandle* node_handle = RclHandle::Unwrap(info[1].As<Napi::Object>());
253+
rcl_node_t* node = reinterpret_cast<rcl_node_t*>(node_handle->ptr());
254+
rcl_clock_t* clock = reinterpret_cast<rcl_clock_t*>(
255+
RclHandle::Unwrap(info[2].As<Napi::Object>())->ptr());
256+
257+
std::string action_name = info[3].As<Napi::String>().Utf8Value();
258+
std::string package_name = info[4].As<Napi::String>().Utf8Value();
259+
const rosidl_action_type_support_t* ts =
260+
GetActionTypeSupport(package_name, action_name);
261+
rcl_ret_t ret = RCL_RET_ERROR;
262+
if (ts) {
263+
rcl_publisher_options_t publisher_ops = rcl_publisher_get_default_options();
264+
auto qos_profile = GetQoSProfile(info[5]);
265+
if (qos_profile) {
266+
publisher_ops.qos = *qos_profile;
267+
}
268+
rcl_service_introspection_state_t state =
269+
static_cast<rcl_service_introspection_state_t>(
270+
info[6].As<Napi::Number>().Uint32Value());
271+
ret = rcl_action_client_configure_action_introspection(
272+
action_client, node, clock, ts, publisher_ops, state);
273+
if (ret == RCL_RET_OK) {
274+
return env.Undefined();
275+
}
276+
}
277+
278+
Napi::Error::New(env, "failed to configure action client introspection")
279+
.ThrowAsJavaScriptException();
280+
return env.Undefined();
281+
}
282+
#endif // ROS_VERSION >= 2505
283+
244284
Napi::Object InitActionClientBindings(Napi::Env env, Napi::Object exports) {
245285
exports.Set("actionCreateClient",
246286
Napi::Function::New(env, ActionCreateClient));
@@ -256,6 +296,10 @@ Napi::Object InitActionClientBindings(Napi::Env env, Napi::Object exports) {
256296
exports.Set("getNumEntities", Napi::Function::New(env, GetNumEntities));
257297
exports.Set("actionSendCancelRequest",
258298
Napi::Function::New(env, ActionSendCancelRequest));
299+
#if ROS_VERSION >= 2505 // ROS2 >= Kilted
300+
exports.Set("configureActionClientIntrospection",
301+
Napi::Function::New(env, ConfigureActionClientIntrospection));
302+
#endif // ROS_VERSION >= 2505
259303
return exports;
260304
}
261305

src/rcl_action_server_bindings.cpp

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
#include <rcl/error_handling.h>
1818
#include <rcl/rcl.h>
19+
#include <rcl_action/action_server.h>
1920
#include <rcl_action/rcl_action.h>
2021

2122
#include <string>
@@ -437,6 +438,46 @@ Napi::Value ActionTakeCancelRequest(const Napi::CallbackInfo& info) {
437438
return env.Undefined();
438439
}
439440

441+
#if ROS_VERSION >= 2505 // ROS2 >= Kilted
442+
Napi::Value ConfigureActionServerIntrospection(const Napi::CallbackInfo& info) {
443+
Napi::Env env = info.Env();
444+
RclHandle* action_server_handle =
445+
RclHandle::Unwrap(info[0].As<Napi::Object>());
446+
rcl_action_server_t* action_server =
447+
reinterpret_cast<rcl_action_server_t*>(action_server_handle->ptr());
448+
RclHandle* node_handle = RclHandle::Unwrap(info[1].As<Napi::Object>());
449+
rcl_node_t* node = reinterpret_cast<rcl_node_t*>(node_handle->ptr());
450+
rcl_clock_t* clock = reinterpret_cast<rcl_clock_t*>(
451+
RclHandle::Unwrap(info[2].As<Napi::Object>())->ptr());
452+
453+
std::string action_name = info[3].As<Napi::String>().Utf8Value();
454+
std::string package_name = info[4].As<Napi::String>().Utf8Value();
455+
const rosidl_action_type_support_t* ts =
456+
GetActionTypeSupport(package_name, action_name);
457+
458+
rcl_ret_t ret = RCL_RET_ERROR;
459+
if (ts) {
460+
rcl_publisher_options_t publisher_ops = rcl_publisher_get_default_options();
461+
auto qos_profile = GetQoSProfile(info[5]);
462+
if (qos_profile) {
463+
publisher_ops.qos = *qos_profile;
464+
}
465+
rcl_service_introspection_state_t state =
466+
static_cast<rcl_service_introspection_state_t>(
467+
info[6].As<Napi::Number>().Uint32Value());
468+
ret = rcl_action_server_configure_action_introspection(
469+
action_server, node, clock, ts, publisher_ops, state);
470+
if (ret == RCL_RET_OK) {
471+
return env.Undefined();
472+
}
473+
}
474+
475+
Napi::Error::New(env, "failed to configure action server introspection")
476+
.ThrowAsJavaScriptException();
477+
return env.Undefined();
478+
}
479+
#endif // ROS_VERSION >= 2505
480+
440481
Napi::Object InitActionServerBindings(Napi::Env env, Napi::Object exports) {
441482
exports.Set("actionCreateServer",
442483
Napi::Function::New(env, ActionCreateServer));
@@ -469,6 +510,10 @@ Napi::Object InitActionServerBindings(Napi::Env env, Napi::Object exports) {
469510
Napi::Function::New(env, ActionServerGoalExists));
470511
exports.Set("actionTakeCancelRequest",
471512
Napi::Function::New(env, ActionTakeCancelRequest));
513+
#if ROS_VERSION >= 2505 // ROS2 >= Kilted
514+
exports.Set("configureActionServerIntrospection",
515+
Napi::Function::New(env, ConfigureActionServerIntrospection));
516+
#endif // ROS_VERSION >= 2505
472517
return exports;
473518
}
474519

test/test-action-client.js

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ const sinon = require('sinon');
1919
const assertUtils = require('./utils.js');
2020
const rclnodejs = require('../index.js');
2121
const { ActionUuid } = require('../index.js');
22+
const { isActionIntrospectionSupported } = require('./utils.js');
2223

2324
describe('rclnodejs action client', function () {
2425
let node;
@@ -295,4 +296,18 @@ describe('rclnodejs action client', function () {
295296

296297
client.destroy();
297298
});
299+
300+
it('Configure introspection', function () {
301+
if (!isActionIntrospectionSupported()) {
302+
this.skip();
303+
}
304+
let client = new rclnodejs.ActionClient(node, fibonacci, 'fibonacci');
305+
const ServiceIntrospectionStates = rclnodejs.ServiceIntrospectionStates;
306+
const QOS = rclnodejs.QoS.profileSystemDefault;
307+
client.configureIntrospection(
308+
node.getClock(),
309+
QOS,
310+
ServiceIntrospectionStates.CONTENTS
311+
);
312+
});
298313
});

test/test-action-server.js

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
const assert = require('assert');
1818
const deepEqual = require('deep-equal');
1919
const assertUtils = require('./utils.js');
20+
const { isActionIntrospectionSupported } = require('./utils.js');
2021
const { randomUUID } = require('crypto');
2122
const rclnodejs = require('../index.js');
2223

@@ -708,4 +709,23 @@ describe('rclnodejs action server', function () {
708709

709710
server.destroy();
710711
});
712+
713+
it('Configure introspection', function () {
714+
if (!isActionIntrospectionSupported()) {
715+
this.skip();
716+
}
717+
let server = new rclnodejs.ActionServer(
718+
node,
719+
fibonacci,
720+
'fibonacci',
721+
() => {}
722+
);
723+
const ServiceIntrospectionStates = rclnodejs.ServiceIntrospectionStates;
724+
const QOS = rclnodejs.QoS.profileSystemDefault;
725+
server.configureIntrospection(
726+
node.getClock(),
727+
QOS,
728+
ServiceIntrospectionStates.CONTENTS
729+
);
730+
});
711731
});

test/types/index.test-d.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -346,6 +346,13 @@ goalHandlePromise.then((goalHandle) => {
346346
expectType<boolean>(goalHandle.isAborted());
347347
});
348348
expectType<object>(actionClient.getNumEntities());
349+
expectType<void>(
350+
actionClient.configureIntrospection(
351+
node.getClock(),
352+
rclnodejs.Node.getDefaultOptions() as rclnodejs.QoS,
353+
rclnodejs.ServiceIntrospectionStates.CONTENTS
354+
)
355+
);
349356

350357
// ---- ActionServer -----
351358
const actionServer = new rclnodejs.ActionServer(
@@ -383,6 +390,13 @@ function executeCallback(
383390

384391
return new Fibonacci.Result();
385392
}
393+
expectType<void>(
394+
actionServer.configureIntrospection(
395+
node.getClock(),
396+
rclnodejs.Node.getDefaultOptions() as rclnodejs.QoS,
397+
rclnodejs.ServiceIntrospectionStates.CONTENTS
398+
)
399+
);
386400

387401
// ---- ActionUuid -----
388402
const actionUuid = new rclnodejs.ActionUuid();

test/utils.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
'use strict';
1616

1717
const assert = require('assert');
18+
const { DistroUtils } = require('../index.js');
1819
const fs = require('fs');
1920
const os = require('os');
2021
const path = require('path');
@@ -86,11 +87,16 @@ function isTypedArray(v) {
8687
return ArrayBuffer.isView(v) && !(v instanceof DataView);
8788
}
8889

90+
function isActionIntrospectionSupported() {
91+
return DistroUtils.getDistroId() > DistroUtils.getDistroId('jazzy');
92+
}
93+
8994
module.exports = {
9095
assertMember: assertMember,
9196
assertThrowsError: assertThrowsError,
9297
createDelay: createDelay,
9398
getAvailablePath: getAvailablePath,
9499
launchPythonProcess: launchPythonProcess,
95100
isTypedArray: isTypedArray,
101+
isActionIntrospectionSupported: isActionIntrospectionSupported,
96102
};

0 commit comments

Comments
 (0)