Node.js Plugin Development

The Node.js implementation provides an asynchronous interface to the DroneEngage DataBus using the same UDP-based protocol as the C++ and Python versions. It is ideal for web-integrated solutions, rapid prototyping, and Node.js-based automation. Source files are in droneengage_databus/nodejs/.

Features

  • Constructor Singletonnew CModule() always returns the same instance

  • async-lock — Reentrant lock for thread-safe sendJMSG / sendBMSG

  • m_OnReceive callback — Direct function reference; no EventEmitter overhead

  • Binary Data SupportsendBMSG with Buffer payload

  • Message Chunking — Automatic split and reassembly via CUDPClient

  • Extensible Parser — Subclass AndruavMessageParserBase to handle commands

  • Graceful Shutdown — SIGINT/SIGTERM handlers with uninit()


Quick Start

Installation

cd droneengage_databus/nodejs
npm install

Required packages (package.json):

  • async-lock ^1.4.1 — reentrant locking for send operations

  • readline ^1.3.0 — interactive console input

Run

node client.js --help
node client.js MyModule 60000 61111    # name, de_comm port, listen port
node client.js MyModule               # uses defaults (60000 / 61111)

Minimal Working Module

const CModule = require('./de_module');
const { CFacade_Base } = require('./de_facade_base');
const { TYPE_AndruavMessage_DUMMY } = require('./messages');

const DEFAULT_UDP_DATABUS_PACKET_SIZE = 8192;

const cModule = new CModule();
const facade = new CFacade_Base();
facade.setModule(cModule);

cModule.defineModule(
    "gen",             // MODULE_CLASS_GENERIC
    "MyModule",        // module_id (display name)
    "unique-key-001",  // module_key (persistent GUID)
    "1.0.0",           // version
    []                 // message_filter (empty = receive nothing)
);

cModule.addModuleFeatures("T");  // MODULE_FEATURE_SENDING_TELEMETRY
cModule.addModuleFeatures("R");  // MODULE_FEATURE_RECEIVING_TELEMETRY

cModule.init("0.0.0.0", 60000, "0.0.0.0", 61111, DEFAULT_UDP_DATABUS_PACKET_SIZE);

setInterval(() => {
    cModule.sendJMSG("", { t: "hello" }, TYPE_AndruavMessage_DUMMY, true);
}, 1000);

Core Concepts

Singleton Pattern

CModule uses a constructor-based singleton:

const cModule = new CModule();  // always returns the same instance

Receive Callback

Messages are delivered via a property assignment — not via EventEmitter:

cModule.m_OnReceive = (message, length, jMsg) => {
    const msgType = jMsg[ANDRUAV_PROTOCOL_MESSAGE_TYPE];
    const cmd     = jMsg[ANDRUAV_PROTOCOL_MESSAGE_CMD];
    // dispatch on msgType ...
};

Callback Chain

CUDPClient (chunk reassembly)
  → CModule.onReceive(message, len)
      → validates routing / handles TYPE_AndruavModule_ID (sets m_party_id / m_group_id)
      → calls m_OnReceive(message, len, jMsg)   ← your callback
          → optional: parser.parseMessage(jMsg, message, len)
              → parseCommand() / parseRemoteExecute()  ← your overrides

async-lock Thread Safety

sendJMSG and sendBMSG acquire a named lock before building and sending:

this.m_lock.acquire('lock', (done) => {
    // build message, call sendMSG
    done();
});

Complete API Reference

CModule class

const CModule = require('./de_module');
const cModule = new CModule();

Initialization

defineModule(module_class, module_id, module_key, module_version, message_filter)
ParameterTypeDescription
module_classstring"gen", "fcb", "camera", etc.
module_idstringDisplay name shown in WebClient
module_keystringUnique persistent GUID
module_versionstringVersion string e.g. "1.0.0"
message_filternumber[]Array of TYPE_* ints; [] = receive none
init(target_ip, broadcasts_port, host, listening_port, chunk_size)
ParameterDescription
target_ipCommunicator IP ("0.0.0.0" = localhost)
broadcasts_portCommunicator port (typically 60000)
hostLocal bind address ("0.0.0.0")
listening_portLocal receive port (unique per module)
chunk_sizeMax UDP payload (DEFAULT_UDP_DATABUS_PACKET_SIZE = 8192)
uninit()   // stops UDP client; call before process.exit()

Module Configuration

addModuleFeatures(feature)      // "R", "T", "C", "V", "G", "A", "K", "P"
setHardware(hardware_serial, hardware_serial_type)  // serial string, 0=undef / 1=CPU
appendExtraField(name, value)   // add custom fields to the ID broadcast packet

Sending

// Send JSON message
sendJMSG(targetPartyID, jmsg, andruav_message_id, internal_message)
ParameterDescription
targetPartyID"" = broadcast; or specific party ID
jmsgPlain object — the message payload
andruav_message_idTYPE_* constant from messages.js
internal_messagetrue = intermodule; false = group/individual
// Send binary message (JSON header + null byte + binary payload)
sendBMSG(targetPartyID, bmsg, bmsg_length, andruav_message_id, internal_message, message_cmd)
ParameterDescription
bmsgBuffer containing binary data
bmsg_lengthLength of bmsg
message_cmdMetadata object placed in the JSON header
sendSysMsg(jmsg, andruav_message_id)     // system message to communicator
sendMREMSG(command_type)                 // module remote-execute command
forwardMSG(message, datalength)          // forward a raw buffer
sendMSG(msg, length)                     // low-level raw send

Receiving

// Assign your callback:
cModule.m_OnReceive = (message, length, jMsg) => { ... };

jMsg fields:

ConstantDescription
ANDRUAV_PROTOCOL_MESSAGE_TYPEMessage type integer
ANDRUAV_PROTOCOL_MESSAGE_CMDPayload object
INTERMODULE_ROUTING_TYPERouting type string
ANDRUAV_PROTOCOL_SENDERSender's party ID

State

cModule.m_party_id   // set after communicator responds to registration
cModule.m_group_id   // set after communicator responds to registration
cModule.m_module_key

CFacade_Base class

const { CFacade_Base } = require('./de_facade_base');
const facade = new CFacade_Base();
facade.setModule(cModule);

Methods

setModule(module)   // inject CModule reference

requestID(targetPartyID)   // request ID from target

sendErrorMessage(targetPartyID, errorNumber, infoType, notificationType, description)
notificationTypeValue
NOTIFICATION_TYPE_EMERGENCY0
NOTIFICATION_TYPE_ALERT1
NOTIFICATION_TYPE_CRITICAL2
NOTIFICATION_TYPE_ERROR3
NOTIFICATION_TYPE_WARNING4
NOTIFICATION_TYPE_NOTICE5
NOTIFICATION_TYPE_INFO6
NOTIFICATION_TYPE_DEBUG7
API_sendConfigTemplate(target_party_id, module_key, json_file_content_json, reply)

Extending CFacade_Base

// See my_facade.js for the pattern:
const { CFacade_Base } = require('./de_facade_base');

class CMyFacade extends CFacade_Base {
    constructor(module) {
        super();
        if (module) this.setModule(module);
    }

    sendSensorReading(targetPartyID, value) {
        this.m_module.sendJMSG(targetPartyID,
            { sensor_value: value, ts: Date.now() },
            TYPE_AndruavMessage_DUMMY, false);
    }
}

module.exports = { CMyFacade };

Usage in client.js:

const cModule  = new CModule();
const myFacade = new CMyFacade(cModule);
myFacade.sendErrorMessage("", ERROR_USER_DEFINED, NOTIFICATION_TYPE_INFO,
                          NOTIFICATION_TYPE_INFO, "Hello from Node.js");

AndruavMessageParserBase class

Abstract base for inbound message dispatch. Subclass and override the two abstract methods.

const { AndruavMessageParserBase } = require('./de_message_parser_base');
// Call from your m_OnReceive callback:
parseMessage(andruav_message, full_message, full_message_length)

Must override:

parseRemoteExecute(andruav_message)   // called when TYPE_AndruavMessage_RemoteExecute
parseCommand(andruav_message, full_message, full_message_length, messageType, permission)

State getters:

get isBinary()      // true if message contains binary payload
get isSystem()      // true if sender is the communicator server
get isInterModule() // true if routing type == CMD_TYPE_INTERMODULE

parseMessage automatically handles TYPE_AndruavMessage_CONFIG_ACTION before calling parseCommand.

Example subclass:

const { AndruavMessageParserBase } = require('./de_message_parser_base');
const { TYPE_AndruavMessage_MAVLINK } = require('./messages');

class MyParser extends AndruavMessageParserBase {
    constructor(facade) {
        super();
        this._facade = facade;
    }

    parseRemoteExecute(andruav_message) {
        // handle remote execute commands
    }

    parseCommand(andruav_message, full_message, full_message_length, messageType, permission) {
        if (messageType === TYPE_AndruavMessage_MAVLINK) {
            const mavlinkData = Buffer.from(full_message).slice(/* header offset */);
            // process mavlink bytes
        }
    }
}

Wire up to receive:

const parser = new MyParser(myFacade);
cModule.m_OnReceive = (message, length, jMsg) => {
    parser.parseMessage(jMsg, message, length);
};

Module Class Constants

From messages.js:

const MODULE_CLASS_GENERIC = "gen";
const MODULE_CLASS_FCB     = "fcb";
const MODULE_CLASS_VIDEO   = "camera";
const MODULE_CLASS_P2P     = "p2p";
const MODULE_CLASS_COMM    = "comm";

Module Feature Constants

const MODULE_FEATURE_RECEIVING_TELEMETRY = "R";
const MODULE_FEATURE_SENDING_TELEMETRY   = "T";
const MODULE_FEATURE_CAPTURE_IMAGE       = "C";
const MODULE_FEATURE_CAPTURE_VIDEO       = "V";
const MODULE_FEATURE_GPIO                = "G";
const MODULE_FEATURE_AI_RECOGNITION      = "A";
const MODULE_FEATURE_TRACKING            = "K";
const MODULE_FEATURE_P2P                 = "P";

Message Handling

Message Filter

Pass an array of TYPE_* integers to defineModule. The communicator only forwards matching message types to your module:

const { TYPE_AndruavMessage_GPS, TYPE_AndruavMessage_MAVLINK } = require('./messages');

cModule.defineModule("gen", "GPSMonitor", moduleId, "1.0.0",
    [TYPE_AndruavMessage_GPS, TYPE_AndruavMessage_MAVLINK]);

An empty array [] means the module receives no messages (send-only). Pass null or omit to receive all messages.

Receive Dispatch Pattern

const { ANDRUAV_PROTOCOL_MESSAGE_TYPE, ANDRUAV_PROTOCOL_MESSAGE_CMD,
        TYPE_AndruavMessage_GPS, TYPE_AndruavMessage_POWER } = require('./messages');

cModule.m_OnReceive = (message, length, jMsg) => {
    try {
        const msgType = jMsg[ANDRUAV_PROTOCOL_MESSAGE_TYPE];
        const cmd     = jMsg[ANDRUAV_PROTOCOL_MESSAGE_CMD];

        switch (msgType) {
            case TYPE_AndruavMessage_GPS:
                console.log(`GPS: ${cmd.lat}, ${cmd.lng}`);
                break;
            case TYPE_AndruavMessage_POWER:
                console.log(`Battery: ${cmd.voltage}V`);
                break;
        }
    } catch (e) {
        console.error('Error processing message:', e);
    }
};

Binary Transmission

const fs = require('fs');
const { TYPE_AndruavMessage_IMG } = require('./messages');

const imageData = fs.readFileSync('photo.jpg');
const metadata  = { lat: 31.5, lng: 34.5, alt: 100, tim: Date.now() * 1000 };

cModule.sendBMSG("", imageData, imageData.length,
                 TYPE_AndruavMessage_IMG, false, metadata);

Custom Message Types

const { TYPE_AndruavMessage_USER_RANGE_START } = require('./messages');

const TYPE_MY_SENSOR_DATA = TYPE_AndruavMessage_USER_RANGE_START + 0;
const TYPE_MY_CMD_ACK     = TYPE_AndruavMessage_USER_RANGE_START + 1;

cModule.sendJMSG("", { temperature: 25.5 }, TYPE_MY_SENSOR_DATA, false);

Graceful Shutdown

process.on('SIGINT',  () => cleanupAndExit());
process.on('SIGTERM', () => cleanupAndExit());

function cleanupAndExit() {
    try { cModule.uninit(); } catch (e) {}
    process.exit(0);
}

TypeScript Type Definitions

// types.d.ts
declare module './de_module' {
    export default class CModule {
        m_OnReceive: ((message: Buffer, length: number, jMsg: any) => void) | null;
        m_party_id: string;
        m_group_id: string;

        defineModule(module_class: string, module_id: string, module_key: string,
                     module_version: string, message_filter: number[]): void;
        init(target_ip: string, broadcasts_port: number, host: string,
             listening_port: number, chunk_size: number): boolean;
        uninit(): boolean;
        addModuleFeatures(feature: string): void;
        setHardware(hardware_serial: string, hardware_serial_type: number): void;
        sendJMSG(targetPartyID: string, jmsg: object,
                 andruav_message_id: number, internal_message: boolean): void;
        sendBMSG(targetPartyID: string, bmsg: Buffer, bmsg_length: number,
                 andruav_message_id: number, internal_message: boolean,
                 message_cmd: object): void;
        appendExtraField(name: string, value: any): void;
    }
}

Port Configuration

  • Communicator port: 60000 (default)

  • Module listen port: any unused UDP port; client.js defaults to 61111

  • Use the range 61000–62000 for custom modules to avoid conflicts


Debugging

Enable verbose output by setting DEBUG env var or uncommenting console.log lines in de_module.js:

DEBUG=* node client.js MyModule

Key log lines already present in the source:

console.log(`RX MSG: len ${len}: ${message}`);      // de_module.js onReceive
console.log(`sendJMSG: ${msg}`);                     // de_module.js sendJMSG

Best Practices

  1. Persistent module key — Use a hardcoded or file-stored GUID so the communicator tracks the module across restarts.

  2. Unique listen port — Each running instance needs its own port.

  3. Minimal message filter — Subscribe only to types you handle.

  4. Wrap m_OnReceive in try-catch — Exceptions inside the callback will silently stop message delivery.

  5. Call uninit() before exit — Stops UDP threads cleanly.

  6. Use CMyFacade — Extend CFacade_Base rather than calling cModule.sendJMSG directly from application code.


Troubleshooting

IssueSolution
Module not in WebClient DetailsVerify communicator is on port 60000; check cModule.m_party_id is set
Messages not receivedVerify TYPE_* is in message_filter array
Cannot find module 'async-lock'Run npm install in the nodejs/ directory
Module not reconnecting after communicator restartm_FirstReceived flag prevents repeat ID sends; restart the module
Binary data truncatedEnsure bmsg_length === bmsg.length

See Also