Skip to content

Add namespace support for the new version of Rosbridge_suite #842

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 9 commits into
base: develop
Choose a base branch
from
8 changes: 4 additions & 4 deletions src/core/Param.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,11 +36,11 @@ export default class Param {
get(callback, failedCallback) {
var paramClient = new Service({
ros: this.ros,
name: 'rosapi/get_param',
name: `${this.ros.namespace}rosapi/get_param`,
serviceType: 'rosapi/GetParam'
});

var request = {name: this.name};
var request = { name: this.name };

paramClient.callService(
request,
Expand Down Expand Up @@ -69,7 +69,7 @@ export default class Param {
set(value, callback, failedCallback) {
var paramClient = new Service({
ros: this.ros,
name: 'rosapi/set_param',
name: `${this.ros.namespace}rosapi/set_param`,
serviceType: 'rosapi/SetParam'
});

Expand All @@ -89,7 +89,7 @@ export default class Param {
delete(callback, failedCallback) {
var paramClient = new Service({
ros: this.ros,
name: 'rosapi/delete_param',
name: `${this.ros.namespace}rosapi/delete_param`,
serviceType: 'rosapi/DeleteParam'
});

Expand Down
36 changes: 22 additions & 14 deletions src/core/Ros.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,19 +29,27 @@ export default class Ros extends EventEmitter {
idCounter = 0;
isConnected = false;
groovyCompatibility = true;
namespace = '';
/**
* @param {Object} [options]
* @param {string} [options.url] - The WebSocket URL for rosbridge. Can be specified later with `connect`.
* @param {boolean} [options.groovyCompatibility=true] - Don't use interfaces that changed after the last groovy release or rosbridge_suite and related tools.
* @param {'websocket'|RTCPeerConnection} [options.transportLibrary='websocket'] - 'websocket', or an RTCPeerConnection instance controlling how the connection is created in `connect`.
* @param {Object} [options.transportOptions={}] - The options to use when creating a connection. Currently only used if `transportLibrary` is RTCPeerConnection.
* @param {string} [options.namespace = ""] - The namespace to use for internal ROS2 services. Defaults to an empty string. Must be the same as the namespace of rosbridge_suite.
*/
constructor(options) {
super();
options = options || {};
this.transportLibrary = options.transportLibrary || 'websocket';
this.transportOptions = options.transportOptions || {};
this.groovyCompatibility = options.groovyCompatibility ?? true;
this.namespace = options.namespace || '';

// Normalize namespace format: with trailing slash
if (this.namespace) {
this.namespace = this.namespace.replace(/\/?$/, '/');
}

// begin by checking if a URL was given
if (options.url) {
Expand Down Expand Up @@ -180,7 +188,7 @@ export default class Ros extends EventEmitter {
/** @satisfies {Service<any, any>} */
var getActionServers = new Service({
ros: this,
name: 'rosapi/action_servers',
name: `${this.namespace}rosapi/action_servers`,
serviceType: 'rosapi/GetActionServers'
});

Expand Down Expand Up @@ -220,7 +228,7 @@ export default class Ros extends EventEmitter {
getTopics(callback, failedCallback) {
var topicsClient = new Service({
ros: this,
name: 'rosapi/topics',
name: `${this.namespace}rosapi/topics`,
serviceType: 'rosapi/Topics'
});

Expand Down Expand Up @@ -259,7 +267,7 @@ export default class Ros extends EventEmitter {
getTopicsForType(topicType, callback, failedCallback) {
var topicsForTypeClient = new Service({
ros: this,
name: 'rosapi/topics_for_type',
name: `${this.namespace}rosapi/topics_for_type`,
serviceType: 'rosapi/TopicsForType'
});

Expand Down Expand Up @@ -299,7 +307,7 @@ export default class Ros extends EventEmitter {
getServices(callback, failedCallback) {
var servicesClient = new Service({
ros: this,
name: 'rosapi/services',
name: `${this.namespace}rosapi/services`,
serviceType: 'rosapi/Services'
});

Expand Down Expand Up @@ -338,7 +346,7 @@ export default class Ros extends EventEmitter {
getServicesForType(serviceType, callback, failedCallback) {
var servicesForTypeClient = new Service({
ros: this,
name: 'rosapi/services_for_type',
name: `${this.namespace}rosapi/services_for_type`,
serviceType: 'rosapi/ServicesForType'
});

Expand Down Expand Up @@ -380,7 +388,7 @@ export default class Ros extends EventEmitter {
getServiceRequestDetails(type, callback, failedCallback) {
var serviceTypeClient = new Service({
ros: this,
name: 'rosapi/service_request_details',
name: `${this.namespace}rosapi/service_request_details`,
serviceType: 'rosapi/ServiceRequestDetails'
});
var request = {
Expand Down Expand Up @@ -422,7 +430,7 @@ export default class Ros extends EventEmitter {
/** @satisfies {Service<{},{typedefs: string[]}>} */
var serviceTypeClient = new Service({
ros: this,
name: 'rosapi/service_response_details',
name: `${this.namespace}rosapi/service_response_details`,
serviceType: 'rosapi/ServiceResponseDetails'
});
var request = {
Expand Down Expand Up @@ -462,7 +470,7 @@ export default class Ros extends EventEmitter {
getNodes(callback, failedCallback) {
var nodesClient = new Service({
ros: this,
name: 'rosapi/nodes',
name: `${this.namespace}rosapi/nodes`,
serviceType: 'rosapi/Nodes'
});

Expand Down Expand Up @@ -522,7 +530,7 @@ export default class Ros extends EventEmitter {
getNodeDetails(node, callback, failedCallback) {
var nodesClient = new Service({
ros: this,
name: 'rosapi/node_details',
name: `${this.namespace}rosapi/node_details`,
serviceType: 'rosapi/NodeDetails'
});

Expand Down Expand Up @@ -563,7 +571,7 @@ export default class Ros extends EventEmitter {
getParams(callback, failedCallback) {
var paramsClient = new Service({
ros: this,
name: 'rosapi/get_param_names',
name: `${this.namespace}rosapi/get_param_names`,
serviceType: 'rosapi/GetParamNames'
});
var request = {};
Expand Down Expand Up @@ -601,7 +609,7 @@ export default class Ros extends EventEmitter {
getTopicType(topic, callback, failedCallback) {
var topicTypeClient = new Service({
ros: this,
name: 'rosapi/topic_type',
name: `${this.namespace}rosapi/topic_type`,
serviceType: 'rosapi/TopicType'
});
var request = {
Expand Down Expand Up @@ -642,7 +650,7 @@ export default class Ros extends EventEmitter {
getServiceType(service, callback, failedCallback) {
var serviceTypeClient = new Service({
ros: this,
name: 'rosapi/service_type',
name: `${this.namespace}rosapi/service_type`,
serviceType: 'rosapi/ServiceType'
});
var request = {
Expand Down Expand Up @@ -683,7 +691,7 @@ export default class Ros extends EventEmitter {
getMessageDetails(message, callback, failedCallback) {
var messageDetailClient = new Service({
ros: this,
name: 'rosapi/message_details',
name: `${this.namespace}rosapi/message_details`,
serviceType: 'rosapi/MessageDetails'
});
var request = {
Expand Down Expand Up @@ -775,7 +783,7 @@ export default class Ros extends EventEmitter {
getTopicsAndRawTypes(callback, failedCallback) {
var topicsAndRawTypesClient = new Service({
ros: this,
name: 'rosapi/topics_and_raw_types',
name: `${this.namespace}rosapi/topics_and_raw_types`,
serviceType: 'rosapi/TopicsAndRawTypes'
});

Expand Down
16 changes: 15 additions & 1 deletion test/examples/setup_examples.launch
Original file line number Diff line number Diff line change
@@ -1,5 +1,19 @@
<launch>
<include file="$(find rosbridge_server)/launch/rosbridge_websocket.launch" />
<include file="$(find rosbridge_server)/launch/rosbridge_websocket.launch">
<arg name="port" value="9090" />
</include>

<!-- Second rosbridge instance on different port -->
<include file="$(find rosbridge_server)/launch/rosbridge_websocket.launch">
<arg name="port" value="9091" />
<arg name="namespace" value="hello" />
</include>

<include file="$(find rosbridge_server)/launch/rosbridge_websocket.launch">
<arg name="port" value="9092" />
<arg name="namespace" value="hello/world" />
</include>

<node name="tf_publisher" pkg="tf" type="static_transform_publisher" args="0 0 0 0 0 0 /world /turtle1 100" />
<node name="tf2_web_republisher" pkg="tf2_web_republisher" type="tf2_web_republisher" />
<node name="fibonacci_server" pkg="actionlib_tutorials" type="fibonacci_server" />
Expand Down
128 changes: 128 additions & 0 deletions test/namespaces.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
import { it, describe, expect } from 'vitest';
import { Ros } from '../';

describe('Namespaces', () => {

it('Can retrieve a list of topics using a namespace using a trailing slash', async () => {

const ros = new Ros({
url: 'ws://localhost:9091',
namespace: 'hello/'
});

const topics = await new Promise((resolve, reject) => {
ros.getTopics(resolve, reject);
});

// we expect the topics object to not be empty
expect(topics).be.an('object');
expect(topics).not.hasLength(0);

});

it('Can retrieve a list of topics using a namespace with a leading slash', async () => {

const ros = new Ros({
url: 'ws://localhost:9091',
namespace: '/hello/'
});

const topics = await new Promise((resolve, reject) => {
ros.getTopics(resolve, reject);
});

expect(topics).be.an('object');
expect(topics).not.hasLength(0);
});

it('Can retrieve a list of topics using a namespace with a leading and no trailing slash', async () => {

const ros = new Ros({
url: 'ws://localhost:9091',
namespace: '/hello'
});

const topics = await new Promise((resolve, reject) => {
ros.getTopics(resolve, reject);
});

expect(topics).be.an('object');
expect(topics).not.hasLength(0);
})

it('Can retrieve a list of topics using a nested namespace', async () => {

const ros = new Ros({
url: 'ws://localhost:9092',
namespace: 'hello/world/'
});

const topics = await new Promise((resolve, reject) => {
ros.getTopics(resolve, reject);
});

expect(topics).be.an('object');
expect(topics).not.hasLength(0);
});

it('Can retrieve a list of topics using a nested namespace with a leading slash', async () => {

const ros = new Ros({
url: 'ws://localhost:9092',
namespace: '/hello/world/'
});

const topics = await new Promise((resolve, reject) => {
ros.getTopics(resolve, reject);
});

expect(topics).be.an('object');
expect(topics).not.hasLength(0);
});

it('Can retrieve a list of topics using a nested namespace with a leading and no trailing slash', async () => {

const ros = new Ros({
url: 'ws://localhost:9092',
namespace: '/hello/world'
});

const topics = await new Promise((resolve, reject) => {
ros.getTopics(resolve, reject);
});

expect(topics).be.an('object');
expect(topics).not.hasLength(0);
});


it('Can retrieve a list of topics using an empty namespaces', async () => {

const ros = new Ros({
url: 'ws://localhost:9090',
namespace: ''
});

const topics = await new Promise((resolve, reject) => {
ros.getTopics(resolve, reject);
});

expect(topics).be.an('object');
expect(topics).not.hasLength(0);
})

it('Can retrieve a list of topics using an empty namespace, not set in constructor', async () => {

const ros = new Ros({
url: 'ws://localhost:9090'
});

const topics = await new Promise((resolve, reject) => {
ros.getTopics(resolve, reject);
});

expect(topics).be.an('object');
expect(topics).not.hasLength(0);
})

})
Loading