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 Singleton —
new CModule()always returns the same instanceasync-lock— Reentrant lock for thread-safesendJMSG/sendBMSGm_OnReceivecallback — Direct function reference; no EventEmitter overheadBinary Data Support —
sendBMSGwithBufferpayloadMessage Chunking — Automatic split and reassembly via
CUDPClientExtensible Parser — Subclass
AndruavMessageParserBaseto handle commandsGraceful 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 operationsreadline^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)
| Parameter | Type | Description |
|---|---|---|
module_class | string | "gen", "fcb", "camera", etc. |
module_id | string | Display name shown in WebClient |
module_key | string | Unique persistent GUID |
module_version | string | Version string e.g. "1.0.0" |
message_filter | number[] | Array of TYPE_* ints; [] = receive none |
init(target_ip, broadcasts_port, host, listening_port, chunk_size)
| Parameter | Description |
|---|---|
target_ip | Communicator IP ("0.0.0.0" = localhost) |
broadcasts_port | Communicator port (typically 60000) |
host | Local bind address ("0.0.0.0") |
listening_port | Local receive port (unique per module) |
chunk_size | Max 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)
| Parameter | Description |
|---|---|
targetPartyID | "" = broadcast; or specific party ID |
jmsg | Plain object — the message payload |
andruav_message_id | TYPE_* constant from messages.js |
internal_message | true = 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)
| Parameter | Description |
|---|---|
bmsg | Buffer containing binary data |
bmsg_length | Length of bmsg |
message_cmd | Metadata 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:
| Constant | Description |
|---|---|
ANDRUAV_PROTOCOL_MESSAGE_TYPE | Message type integer |
ANDRUAV_PROTOCOL_MESSAGE_CMD | Payload object |
INTERMODULE_ROUTING_TYPE | Routing type string |
ANDRUAV_PROTOCOL_SENDER | Sender'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)
notificationType | Value |
|---|---|
NOTIFICATION_TYPE_EMERGENCY | 0 |
NOTIFICATION_TYPE_ALERT | 1 |
NOTIFICATION_TYPE_CRITICAL | 2 |
NOTIFICATION_TYPE_ERROR | 3 |
NOTIFICATION_TYPE_WARNING | 4 |
NOTIFICATION_TYPE_NOTICE | 5 |
NOTIFICATION_TYPE_INFO | 6 |
NOTIFICATION_TYPE_DEBUG | 7 |
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.jsdefaults to61111Use 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
Persistent module key — Use a hardcoded or file-stored GUID so the communicator tracks the module across restarts.
Unique listen port — Each running instance needs its own port.
Minimal message filter — Subscribe only to types you handle.
Wrap
m_OnReceivein try-catch — Exceptions inside the callback will silently stop message delivery.Call
uninit()before exit — Stops UDP threads cleanly.Use
CMyFacade— ExtendCFacade_Baserather than callingcModule.sendJMSGdirectly from application code.
Troubleshooting
| Issue | Solution |
|---|---|
| Module not in WebClient Details | Verify communicator is on port 60000; check cModule.m_party_id is set |
| Messages not received | Verify TYPE_* is in message_filter array |
Cannot find module 'async-lock' | Run npm install in the nodejs/ directory |
| Module not reconnecting after communicator restart | m_FirstReceived flag prevents repeat ID sends; restart the module |
| Binary data truncated | Ensure bmsg_length === bmsg.length |