.. _`Node.js API for Vortex DDS`: ########################## 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* 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 :ref:`QoS Provider`. 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. 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. 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. 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. 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. 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** .. code-block:: javascript const dds = require('vortexdds'); // create domain participant const participant = new dds.Participant(); **Example: Create new participant on the default domain with a QoS profile** .. code-block:: javascript 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); }); } } } .. _Topic: 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. .. code-block:: javascript 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** .. code-block:: javascript 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); }); } } }; 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** .. code-block:: javascript //... 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. .. code-block:: javascript //... // get publisher qos from previously created qos provider: qp const pubqos = qp.getPublisherQos(); // create a publisher const publisher = participant.createPublisher(pubqos); 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** .. code-block:: javascript 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 } 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** .. code-block:: javascript //... //create Subscriber const sub = participant.createSubscriber(); Create a subscriber with participant and QoS profile. .. code-block:: javascript //... // get subscriber qos from previously created qos provider: qp const subqos = qp.getSubscriberQos(); // create a subscriber const subscriber = participant.createSubscriber(subqos); 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*** .. code-block:: javascript 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); //... } 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** .. code-block:: javascript 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(); } } 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** .. code-block:: javascript 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); //... } 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** .. code-block:: javascript 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(); } } } 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** .. code-block:: javascript const dds = require('vortexdds'); //... const condition = squareWriter.createStatusCondition( dds.StatusMask.publication_matched ); //... .. raw:: latex \newpage 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. .. code-block:: javascript 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();