5. Node.js API for Vortex DDS

The Node.js DCPS API provides users with Node.js classes to model DDS communication using JavaScript and pure DDS applications.

The Node.js DCPS API consists of one module.

  • vortexdds

This section provides an overview of the main DDS concepts and Node.js API examples for these DDS concepts.

Note

  • The Node.js DCPS API documentation can be found in the following directory:

    $OSPL_HOME/docs/nodejs/html

5.1. API Usage Patterns

The typical usage pattern for the Node.js DCPS API for Vortex DDS is the following:

  • Model your DDS topics using IDL and generate Node.js topic classes from IDL.
  • AND/OR generate Node.js topic classes for topics that already exist in the DDS system.
  • Start writing your Node.js program using the Node.js API for Vortex DDS.

The core classes are Participant, Topic, Reader and Writer. Publisher and Subscriber classes can be used to adjust the Quality of Service (QoS) defaults.

For details on setting QoS values with the API, see Quality of Service (QoS).

The following list shows the sequence in which you would use the Vortex classes:

  • Create a Participant instance.
  • Create one or more Topic using the Participant instance.
  • If you require publisher or subscriber level non-default QoS settings, create Publisher and/or Subscriber using the Participant instance. (The most common reason for changing publisher/subscriber QoS is to define non-default partitions.)
  • Create Reader and/or Writer classes using the Topic instances that you created.
  • If you required data filtering, create a QueryCondition using the Reader instance.
  • Create the core of program, writing and/or reading data and processing it.

5.1.1. Asynchronous Aspects of the API

Some DDS operations can take a long time. In order to not block the execution of other asynchronous JavaScript operation, the Vortex DDS API for NodeJS uses asynchronous operations the return standard JavaScript Promise objects.

In addition, at creation, some entities accept a ‘listener’ object, which defines one or more ‘callback’ methods, which are called asynchronously by the NodeJS engine when the appropriate event occurs. Entities supporting listeners are: Topic, Reader and Writer. See their factory methods for documentation on these listeners.

5.1.2. Releasing DDS resources

Many DDS objects have associated with them resources obtained from the DDS system. The NodeJS engine does not reclaim these resources, even if it garbage collects an object the represents such a resource.

In order to avoid leaking of DDS resources, you should take care to call the appropriate delete() method when you are finished with a DDS object. Once the delete() method is called on an object, it is no longer usable.

Many DDS objects are organized into a hierarchy. DDS objects created by factory methods on another DDS object are implicitly ‘owned’ by that ‘parent’ object. If a parent object is deleted, then all directly and indirectly owned objects are also deleted. The only DDS types that are not ‘owned’ by other objects are:

  • Participant, which sit at the top of the ownership hierarchy
  • Waitset instances.
  • GuardCondition instances.
  • QoSProvider instances.

To completely clean-up DDS resources, at a minimum, your program must explicitly delete all instances of the above types. Instances of other DDS objects may be explicitly deleted by your program once you no longer require them. Explicitly deleted such objects will reduce the footprint of your DDS application.

5.1.3. Exceptions

Most APIs will throw exceptions, rather than return ‘error codes’.

The most common exception thrown is DDSError, which represents an error within the DDS system. Frequently, additional information is written to the file dds-error.log. All methods that throw DDSError have this fact explicitly documented.

Most methods also type-check their arguments. If errors are found in these arguments, then a standard JavaScript TypeError is typically throw. The documentation does not explicitly document when TypeError is thrown.

5.1.4. Key API References

  • To import data types defined in an Vortex DDS compliant IDL file, see importIDL().
  • To connect to a DDS domain, create a Participant.
  • To register a DDS topic, use Participant.createTopic().
  • To create a DDS data reader, use Participant.createReader() or Subscriber.createReader().
  • To create a DDS data writer, use Participant.createWriter() or Subscriber.createWriter().
  • Import a externally defined quality-of-service (QoS) ‘profiles’, see QoSProvider.
  • To programmatically create or examine quality-of-service (QoS) policies on an entity, see QoS and Entity.qos.
  • To create a DDS ‘waitset’ that enables you to wait (asynchronously) for specific conditions, see Waitset.

5.2. Participant

The Node.js Participant class represents a DDS domain participant entity.

In DDS - “A domain participant represents the local membership of the application in a domain. A domain is a distributed concept that links all the applications able to communicate with each other. It represents a communication plane: only the publishers and subscribers attached to the same domain may interact.”

A DDS domain participant, represents a connection by your program to a DDS Domain.

Typically, your application will create only one participant. All other DDS entities are created through a participant or one of its child entities via factory methods such as Participant.createPublisher().

Note

An explicit delete() is required for the Participant to release DDS resources. All entities owned directly or indirectly by the Participant will be released on delete().

Example: Create new participant

const dds = require('vortexdds');

// create domain participant
const participant = new dds.Participant();

Example: Create new participant on the default domain with a QoS profile

const dds = require('vortexdds');
const path = require('path');

const QOS_XML_PATH = __dirname + path.sep + 'DDS_Get_Set_QoS.xml';
const QOS_PROFILE = 'DDS GetSetQosProfile';
const DOMAIN_ID = dds.DDS_DOMAIN_DEFAULT;

//...

async function setup(){

    let participant = null;
    let qp = null;
    try {
        // create a qos provider using qos xml file
        qp = new dds.QoSProvider(QOS_XML_PATH, QOS_PROFILE);

        // get participant qos from qos provider and create a participant
        const pqos = qp.getParticipantQos();

        participant = new dds.Participant(DOMAIN_ID, pqos);

        //...

    } finally {
        console.log('=== Cleanup resources');
        if (qp !== null){
        qp.delete();
        }
        if (participant !== null){
          participant.delete().catch((error) => {
            console.log('Error cleaning up resources: '
              + error.message);
          });
        }
    }
}

5.3. Topic

The Node.js Topic class represents a DDS topic type.

A Topic represents a globally named data type, along with quality-of-service policies. The Topic is available to all participants in a DDS domain, and must be registered with the same data type definition and QoS policies.

You must create a Topic before you can create Reader or Writer instances.

Class instances are created by the Participant.createTopic() function.

createTopic(
        topicName,
        typeSupport,
        qos = null,
        listener = null
)

In order to create a Topic, a TypeSupport instance must be created from an IDL file.

Step 1 - Generate TypeSupport objects from IDL file

The function importIDL(idlPath) is provided to generate a TypeSupport instance for every topic defined in an IDL file. This function returns a promise, therefore should be called from an async function.

Step 2 - Create Topic instance using TypeSupport

The createTopic method in the Participant class can then be used to create a topic instance.

Example: Create a topic

const dds = require('vortexdds');
const path = require('path');

//...

async function publishData(){

  console.log('=== HelloWorldPublisher start');

  let participant = null;
  try {
    participant = new dds.Participant();

    const topicName = 'HelloWorldData_Msg';
    const idlName = 'HelloWorldData.idl';
    const idlPath = __dirname + path.sep + idlName;

    const typeSupports = await dds.importIDL(idlPath);
    const typeSupport = typeSupports.get('HelloWorldData::Msg');

    const tqos = dds.QoS.topicDefault();

    tqos.durability = {kind: dds.DurabilityKind.Transient};
    tqos.reliability = {kind: dds.ReliabilityKind.Reliable};

    const topic = participant.createTopic(
        topicName,
        typeSupport,
        tqos
    );

    //...

  } finally {
    console.log('=== Cleanup resources');
    if (participant !== null){
      participant.delete().catch((error) => {
        console.log('Error cleaning up resources: '
          + error.message);
      });
    }
  }
};

5.4. Publisher

The Node.js Publisher class represents a DDS publisher entity.

A Publisher typically owns one or more Writer instances created via the Publisher.createWriter() method.

Class instances are created by the Participant.createPublisher() method.

Use of the Publisher class is optional. You typically create a Publisher because you require quality-of-service parameters not available on the ‘default publisher’. Frequently, you wall want to specify a QoS.partition policy so that non-default partitions are used by the contained Writer instances.

Note

An explicit delete() is not required for the Publisher. When delete() is called on the owning Participant, the Publisher delete() is triggered.

Example: Create a Publisher

//...

const pub = participant.createPublisher();

Create a publisher with a participant and QoS profile. Consider the code snippet below taken from example: GetSetQoSExample/GetSetQoSExample.js file.

//...

// get publisher qos from previously created qos provider: qp
const pubqos = qp.getPublisherQos();

// create a publisher
const publisher = participant.createPublisher(pubqos);

5.5. Writer

The Node.js Writer class represents a DDS data writer entity.

A Writer may be owned by either a Participant or Publisher. Using a Publisher to create a Writer allows the writer to benefit from quality-of-service policies assigned the the Publisher, in particular the QoS.partition policy.

Class instances are created by the Participant.createWriter() or Publisher.createWriter() methods.

Note

An explicit delete() is not required for the Writer. When delete() is called on the owning Participant, the Writer delete() is triggered.

Example: Create a Writer and write sample

const dds = require('vortexdds');

//...

async function writeData(publisher, topic){
    const wqos = dds.QoS.writerDefault();

    wqos.durability = {kind: dds.DurabilityKind.Transient};
    wqos.reliability = {kind: dds.ReliabilityKind.Reliable};
    const writer = publisher.createWriter(topic, wqos);

    // send one message
    const msg = {userID: 1, message: 'Hello World'};
    await writer.writeReliable(msg);

    //writer will be deleted on participant delete
}

5.6. Subscriber

The Node.js Subscriber class represents a DDS subscriber entity.

A Subscriber typically owns one or more Reader instances created via the Subscriber.createReader() method.

Class instances are created by the Participant.createSubscriber() method.

You typically create a Subscriber because you require quality-of-service parameters not available on the ‘default subscriber’. Frequently, you will want to specify a QoS.partition policy so that non-default partitions are used by the contained Reader instances.

Note

An explicit delete() is not required for the Subscriber. When delete() is called on the owning Participant, the Subscriber delete() is triggered.

Example: Create a Subscriber

//...

//create Subscriber
const sub = participant.createSubscriber();

Create a subscriber with participant and QoS profile.

//...

// get subscriber qos from previously created qos provider: qp
const subqos = qp.getSubscriberQos();

// create a subscriber
const subscriber = participant.createSubscriber(subqos);

5.7. Reader

The Node.js Reader class represents a DDS data reader entity.

A Reader may be owned by either a Participant or Subscriber. Using a Subscriber to create a Reader allows the reader to benefit from quality-of-service policies assigned the the Subscriber, in particular the QoS.partition policy.

Class instances are created by the Participant.createReader() or Subscriber.createReader() methods.

Note

An explicit delete() is not required for the Reader. When delete() is called on the owning Participant, the Reader delete() is triggered.

Example: Create a reader and take data*

const dds = require('vortexdds');

//...

function readData(subscriber, topic) {
    const rqos = dds.QoS.readerDefault();

    rqos.durability = {kind: dds.DurabilityKind.Transient};
    rqos.reliability = {kind: dds.ReliabilityKind.Reliable};
    const reader = subscriber.createReader(topic, rqos);

    let takeArray = reader.take(10);

    //...

}

5.7.1. QueryCondition

QueryCondition class represents a condition on Reader input based on samples, instances and view state AND on values found in the actual samples.

A query condition is attached to the Reader entity and created using the Reader.createQueryCondition() function.

Note

An explicit delete() is not required for the QueryCondition. When delete() is called on the owning Reader, the QueryCondition delete() is triggered.

Example: Read using a QueryCondition

const dds = require('vortexdds');

//...

async function readBlueCircleWriteRedSquare(
  circleReader,
  squareWriter
) {
  let queryCond = null;
  let queryWaitset = null;
  try {
    // set up a waitset on our circle reader for the query condition
    // (shape read = blue circle)
    const mask = dds.StateMask.sample.not_read;
    const sqlExpression = 'color=%0';
    const params = ['BLUE'];

    queryCond = circleReader.createQueryCondition(mask, sqlExpression, params);
    queryWaitset = new dds.Waitset(queryCond);

    for (let i = 0; i < 100; i++) {
      await queryWaitset.wait(10000000000);
      let sampleArray = circleReader.takeCond(1, queryCond);
      if (sampleArray.length > 0 && sampleArray[0].info.valid_data) {
        let sample = sampleArray[0].sample;
        console.log(
          util.format(
            '%s %s of size %d at (%d,%d)',
            sample.color,
            'Circle',
            sample.shapesize,
            sample.x,
            sample.y
          )
        );
        squareWriter.write({
          color: 'RED',
          x: sample.x,
          y: sample.y,
          shapesize: 45,
        });
      }
    }

  } finally {
    if (queryWaitset !== null){
      queryWaitset.delete();
    }
  }

5.7.2. ReadCondition

ReadCondition represents a condition on Reader input based on samples, instances and view state.

ReadCondition is used by the Reader to wait for the availablity of data based on a condition. A read condition is attached to the Reader entity and created using the Reader.createReadCondition() function.

Note

An explicit delete() is not required for the ReadCondition. When delete() is called on the owning Reader, the ReadCondition delete() is triggered.

Example: Read data with read condition

const dds = require('vortexdds');

//...

function readCondition(reader) {

    const maxSamples = 100;
    const cond = reader.createReadCondition(dds.StateMask.sample.not_read);
    const readArray = reader.readCond(maxSamples, cond);

    //...
}

5.8. WaitSet

WaitSet class represents a collection of Conditions upon which you can wait.

A WaitSet object allows an application to wait until one or more of the attached Condition objects evaluates to true or until the timeout expires.

Note

An explicit delete() is required for the WaitSet to release DDS resources.

Example Create a WaitSet with a ReadCondition

const dds = require('vortexdds');

//...

async function waitExample(reader) {

    let newDataCondition = null;
    let newDataWaitset = null;
    try {
      // create waitset for new data
      newDataCondition = reader.createReadCondition(
        dds.StateMask.sample.not_read
      );
      newDataWaitset = new dds.Waitset(newDataCondition);

      await newDataWaitset.wait();

      //...

      } finally {
          if (newDataWaitset !== null){
            newDataWaitset.delete();
          }
      }
}

5.9. StatusCondition

StatusCondition class represents a condition on an Entities ‘communication statuses’.

It is created using the createStatusCondition() function on a dds entity instance.

Note

An explicit delete() is not required for the StatusCondition. When delete() is called on the owning Entity, the StatusCondition delete() is triggered.

Example: Create a StatusCondition

const dds = require('vortexdds');

//...

const condition = squareWriter.createStatusCondition(
    dds.StatusMask.publication_matched
);

//...

5.10. GuardCondition

GuardCondition class is a user-triggerable condition for interrupting Waitsets.

A GuardCondition, when attached to a Waitset, enables you to interrupt an asynchronous Waitset.wait() operation by calling the GuardCondition.trigger() method.

Note

An explicit delete() is required for the GuardCondition to release DDS resources.

Example

Create a guard condition and attach it to a waitset.

const dds = require('vortexdds');

const ws = new dds.Waitset();
const guard = new dds.GuardCondition();
ws.attach(guard);
// ws.attach(other conditions);
ws.wait()
.then(triggeredConds => {
    for(const cond of triggeredConds) {
        if(cond === guard) {
            // guard was triggered
        }
    }
})
.catch(err => {
    // wait set error, including timeout
});

// sometime later, cause the wait set to trigger.
guard.trigger();