C++ Plugin Development

The C++ implementation provides high-performance, low-latency communication with the DroneEngage DataBus. It’s ideal for performance-critical applications and hardware integration.

Features

  • C++17 Standard - Modern C++ features and best practices

  • High Performance - Optimized for real-time applications

  • Comprehensive Examples - Sample applications for various use cases

  • Complete API Reference - Detailed documentation of all classes

  • Binary Data Transmission - Efficient handling of images and files

  • MAVLink Integration - Built-in support for MAVLink messages

  • Adaptive Rate Control - Dynamic data rate management

  • Queue-based Processing - Asynchronous message handling

Quick Start

Installation

cd client
mkdir build && cd build
cmake ..
make

Basic Usage

./client --help                    # Show comprehensive help
./client MyModule 60000 61111      # Run with all arguments
./client MyModule                  # Uses default ports

Architecture

The C++ implementation consists of several key components:

Core Classes

CModule

  • Singleton module manager

  • Handles module registration and message routing

  • Thread-safe operations

CUDPClient

  • UDP communication with automatic chunking

  • Message reassembly for large payloads

  • Periodic module identification broadcasting

FacadeBase

  • Simplified API for response generation

  • Template method pattern for message parsing

  • Observer pattern for message callbacks

Message Parser

  • JSON and binary message processing

  • Command dispatch and handling

  • Error handling and validation

Examples

The C++ implementation includes comprehensive examples demonstrating all aspects of module communication:

1. Basic Module Communication (client.cpp)

Purpose: Demonstrates basic module registration, help system, and periodic message sending.

Usage:

./client [OPTIONS] MODULE_NAME [DE_COMM_PORT] [LISTEN_PORT]
./client sample_mod_cpp 60000 61111
./client CPP_Plugin                    # Uses default ports
./client --help                        # Show help

Key Features:

  • Comprehensive help system with colored output

  • Module registration with random ID generation

  • Flexible argument parsing with defaults

  • Input validation with clear error messages

  • Periodic message transmission (every 1 second)

  • Demonstrates sendJMSG() for JSON messages

  • Empty message filter (receives no messages)

  • Appears in WebClient Details tab

Code Highlights:

// Help system with colored output
void printUsage() {
    std::cout << _INFO_CONSOLE_BOLD_TEXT << "DroneEngage C++ Client Module" << _NORMAL_CONSOLE_TEXT_ << std::endl;
    std::cout << _SUCCESS_CONSOLE_BOLD_TEXT_ << "USAGE:" << _NORMAL_CONSOLE_TEXT_ << std::endl;
    std::cout << "  ./client [OPTIONS] MODULE_NAME [DE_COMM_PORT] [LISTEN_PORT]" << std::endl;
    // ... comprehensive help output
}

// Argument parsing with defaults and validation
if (argc < 2) {
    printUsage();
    return 1;
}

// Parse optional port arguments with error handling
int target_port = (argc >= 3) ? std::stoi(argv[2]) : 60000;
int listen_port = (argc >= 4) ? std::stoi(argv[3]) : 61111;

// Define module with random ID
cModule.defineModule(
    MODULE_CLASS_GENERIC,
    module_name,
    module_id,
    "0.0.1",
    Json_de::array()  // Empty message filter
);

// Add module features
cModule.addModuleFeatures(MODULE_FEATURE_SENDING_TELEMETRY);
cModule.addModuleFeatures(MODULE_FEATURE_RECEIVING_TELEMETRY);

// Initialize connection
cModule.init("0.0.0.0", target_port, "0.0.0.0", listen_port, DEFAULT_UDP_DATABUS_PACKET_SIZE);

// Send JSON message
Json_de message = {{"t", "THIS IS A TEST MESSAGE"}, {"long", "..."}};
cModule.sendJMSG("", message, TYPE_AndruavMessage_DUMMY, true);

2. Binary Data Transmission (image_sender.cpp)

Purpose: Demonstrates sending binary data (images) through the databus.

Usage:

./image_sender <image_file_path>
./image_sender ./img.jpeg

Key Features:

  • Reads binary files (images) from disk

  • Sends binary data using sendBMSG() method

  • Transmits image every 10 seconds

  • Includes metadata (lat, lng, alt, timestamp)

  • Connects to default port 60000

Code Highlights:

// Read binary file
readBinaryFile(file_name, content, content_length);

// Prepare metadata
Json_de msg_cmd;
msg_cmd["lat"] = 0;
msg_cmd["lng"] = 0;
msg_cmd["alt"] = 0;
msg_cmd["tim"] = get_time_usec();

// Send binary message
cModule.sendBMSG("", content, content_length, TYPE_AndruavMessage_IMG, false, msg_cmd);


4. Adaptive Rate Control - Sender (sender_adapter.cpp)

Purpose: Demonstrates adaptive message sending with rate control based on receiver feedback.

Usage:

./sender_adapter <module_name> <DE_COMM_PORT> [rate_ms]
./sender_adapter sender_mod 60000 1000

Key Features:

  • Sends messages at configurable rate (default 1000ms)

  • Listens for rate adjustment commands

  • Uses custom message types (TYPE_AndruavMessage_USER_RANGE_START)

  • Dynamic rate adjustment based on receiver feedback

  • Message counter for tracking

Code Highlights:

// Define custom message types
#define TYPE_CUSTOM_SOME_DATA     TYPE_AndruavMessage_USER_RANGE_START+0
#define TYPE_CUSTOM_CHANGE_RATE   TYPE_AndruavMessage_USER_RANGE_START+1

// Subscribe to custom messages
#define MESSAGE_FILTER {TYPE_AndruavMessage_USER_RANGE_START+1, TYPE_AndruavMessage_USER_RANGE_START+2}

// Handle rate change requests
void onReceive(const char* message, int len, Json_de jMsg) {
    if (msgid == TYPE_CUSTOM_CHANGE_RATE) {
        const int delta_delay = jMsg["ms"]["value"].get<int>();
        if (delta_delay == 0) {
            delay = delay / 2;  // Speed up
        } else {
            delay += delta_delay;  // Adjust rate
        }
    }
}

// Send data with counter
Json_de message = {{"t", "SENDING DATA"}, {"counter", counter}};
cModule.sendJMSG("", message, TYPE_AndruavMessage_USER_RANGE_START, true);

5. Queue-Based Processing - Receiver (receiver_adapter.cpp)

Purpose: Demonstrates message queuing, processing, and adaptive rate control feedback.

Usage:

./receiver_adapter <module_name> <DE_COMM_PORT> [processing_delay_ms]
./receiver_adapter receiver_mod 60000 1000

Key Features:

  • Thread-safe message queue with mutex protection

  • Asynchronous message processing

  • Queue depth monitoring

  • Sends feedback to sender to adjust transmission rate

  • Demonstrates backpressure handling

Code Highlights:

// Thread-safe queue
std::queue<std::vector<char>> messageQueue;
std::mutex messageQueueMutex;
std::condition_variable messageQueueConditionVariable;

// Receive and queue messages
void onReceive(const char* message, int len, Json_de jMsg) {
    std::vector<char> msg(message, message + len);
    std::unique_lock<std::mutex> lock(messageQueueMutex, std::defer_lock);
    
    if (lock.try_lock()) {
        messageQueue.push(msg);
        messageQueueConditionVariable.notify_one();
        lock.unlock();
    }
}

// Process messages and send feedback
void processMessages() {
    while (messageQueue.size() > 0) {
        // Process message with delay
        std::this_thread::sleep_for(std::chrono::milliseconds(delay));
        
        // Adjust sender rate based on queue depth
        if (counter > 2 && diff > 0) {
            sendMsg(+2 * counter);  // Tell sender to slow down
        } else {
            sendMsg(0);  // Tell sender to speed up
        }
    }
}

Common Patterns

Help System Implementation

Modern C++ clients should include comprehensive help systems:

void printUsage() {
    std::cout << _INFO_CONSOLE_BOLD_TEXT << "Application Name" << _NORMAL_CONSOLE_TEXT_ << std::endl;
    std::cout << std::endl;
    std::cout << _SUCCESS_CONSOLE_BOLD_TEXT_ << "USAGE:" << _NORMAL_CONSOLE_TEXT_ << std::endl;
    std::cout << "  ./app [OPTIONS] REQUIRED_ARG [OPTIONAL_ARG1] [OPTIONAL_ARG2]" << std::endl;
    std::cout << std::endl;
    std::cout << _SUCCESS_CONSOLE_BOLD_TEXT_ << "ARGUMENTS:" << _NORMAL_CONSOLE_TEXT_ << std::endl;
    std::cout << "  REQUIRED_ARG    Description of required argument" << std::endl;
    std::cout << "  OPTIONAL_ARG1   Description of optional argument (default: value)" << std::endl;
    std::cout << std::endl;
    std::cout << _SUCCESS_CONSOLE_BOLD_TEXT_ << "OPTIONS:" << _NORMAL_CONSOLE_TEXT_ << std::endl;
    std::cout << "  -h, --help     Show this help message and exit" << std::endl;
}

int main(int argc, char* argv[]) {
    // Check for help flags
    bool showHelp = false;
    if (argc > 1) {
        std::string arg1(argv[1]);
        if (arg1 == "-h" || arg1 == "--help") {
            showHelp = true;
        }
    }

    // Show help if requested or missing required args
    if (showHelp || argc < 2) {
        printUsage();
        return showHelp ? 0 : 1;
    }

    // Parse arguments with validation
    try {
        std::string required_arg = argv[1];
        int optional_arg1 = (argc >= 3) ? std::stoi(argv[2]) : DEFAULT_VALUE;
        // ... continue with application logic
    } catch (const std::exception& e) {
        std::cout << _ERROR_CONSOLE_TEXT_ << "Error: " << e.what() << _NORMAL_CONSOLE_TEXT_ << std::endl;
        return 1;
    }
}

Module Initialization

All examples follow this initialization pattern:

// 1. Get module singleton
CModule& cModule = CModule::getInstance();

// 2. Define module
cModule.defineModule(
    MODULE_CLASS_GENERIC,    // Module class
    "module_name",           // Display name
    "unique_id",             // Unique identifier
    "0.0.1",                 // Version
    Json_de::array()         // Message filter (empty or with types)
);

// 3. Add features (optional)
cModule.addModuleFeatures(MODULE_FEATURE_SENDING_TELEMETRY);
cModule.addModuleFeatures(MODULE_FEATURE_RECEIVING_TELEMETRY);

// 4. Set hardware info (optional)
cModule.setHardware("123456", ENUM_HARDWARE_TYPE::HARDWARE_TYPE_CPU);

// 5. Set receive callback (if receiving messages)
cModule.setMessageOnReceive(&onReceive);

// 6. Initialize UDP connection
cModule.init(
    "0.0.0.0",                          // Server IP (0.0.0.0 = localhost)
    60000,                              // Broker port
    "0.0.0.0",                          // Listen IP
    listen_port,                        // Listen port (unique per module)
    DEFAULT_UDP_DATABUS_PACKET_SIZE     // Max packet size
);

Sending Messages

JSON Messages:

Json_de message = {
    {"field1", "value1"},
    {"field2", 123}
};
cModule.sendJMSG("", message, TYPE_AndruavMessage_DUMMY, true);

Binary Messages:

cModule.sendBMSG("", binary_data, data_length, TYPE_AndruavMessage_IMG, false, metadata_json);

Receiving Messages

void onReceive(const char* message, int len, Json_de jMsg) {
    // Extract message type
    const int msgid = jMsg[ANDRUAV_PROTOCOL_MESSAGE_TYPE].get<int>();
    
    // Handle specific message types
    if (msgid == TYPE_CUSTOM_MESSAGE) {
        // Process message
    }
}

Message Types

Examples use various message types:

  • TYPE_AndruavMessage_DUMMY - Test messages

  • TYPE_AndruavMessage_IMG - Image data

  • TYPE_AndruavMessage_MAVLINK - MAVLink protocol messages

  • TYPE_AndruavMessage_USER_RANGE_START - Custom user-defined messages (start of range)

  • TYPE_AndruavMessage_USER_RANGE_START+N - Custom message types (offset from start)

Port Configuration

Each module must use a unique listening port:

  • Broker Port: Typically 60000 (DroneEngage communicator)

  • Module Listen Ports: Examples use:

    • client.cpp: User-defined (e.g., 61111)

    • image_sender.cpp: 50000

    • mavlink_listener.cpp: 70014

    • sender_adapter.cpp: 70034

    • receiver_adapter.cpp: 70024

Testing Scenarios

Scenario 1: Basic Communication Test

# Terminal 1: Start DroneEngage communicator (port 60000)
# Terminal 2: Run client
./client test_module 60000 61111

Scenario 2: Image Streaming

# Terminal 1: Start DroneEngage communicator
# Terminal 2: Send images
./image_sender ./img.jpeg

Scenario 3: Adaptive Rate Control

# Terminal 1: Start DroneEngage communicator
# Terminal 2: Start receiver
./receiver_adapter receiver_mod 60000 1000
# Terminal 3: Start sender
./sender_adapter sender_mod 60000 500

The sender and receiver will automatically adjust transmission rates based on queue depth.

Architecture Overview

┌─────────────────┐
│  Application    │
│  (Examples)     │
└────────┬────────┘
         │
         ↓
┌─────────────────┐
│   CModule       │  ← Module registration & routing
│   CFacade_Base  │  ← High-level API
└────────┬────────┘
         │
         ↓
┌─────────────────┐
│  CUDPClient     │  ← UDP transport with chunking
└────────┬────────┘
         │
         ↓
┌─────────────────┐
│  de_comm        │  ← DroneEngage Communicator
│  (Port 60000)   │
└─────────────────┘

Debugging

Enable debug output by defining DEBUG or DDEBUG:

g++ -DDEBUG client.cpp -o client

Debug output includes:

  • Message reception logs

  • Message content dumps

  • Queue status

  • Rate adjustments

Thread Safety

The examples demonstrate thread-safe patterns:

  • Mutex protection for shared resources (receiver_adapter.cpp)

  • Condition variables for thread synchronization

  • Lock guards for RAII-style locking

  • Try-lock patterns for non-blocking access

Best Practices

  1. Unique Module IDs: Use generateRandomModuleID() or hardcoded unique IDs

  2. Unique Listen Ports: Each module needs its own port

  3. Message Filters: Subscribe only to needed message types for efficiency

  4. Error Handling: Check return values and handle connection failures

  5. Resource Cleanup: Properly delete allocated memory (see image_sender.cpp)

  6. Rate Limiting: Implement backpressure mechanisms for high-frequency data

  7. Help System: Always include comprehensive help with -h/--help flags

  8. Argument Validation: Validate user input with clear error messages

  9. Default Values: Provide sensible defaults for optional arguments

  10. Colored Output: Use console colors for better user experience

  11. Input Handling: Use robust input handling for user prompts:

    #include <limits>
    std::cout << "Press ENTER to continue ..." << std::endl;
    std::cin.clear();
    std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
    std::cin.get();
    

Troubleshooting

| Issue | Solution | |——-|———-| | Module not appearing in WebClient | Check broker port (60000), ensure communicator is running | | Port already in use | Change listen port to unused value | | Messages not received | Verify message type is in MESSAGE_FILTER array | | Segmentation fault | Check buffer sizes, ensure proper initialization | | High CPU usage | Add sleep delays in main loops |

Build System

CMake Configuration

The project uses CMake for cross-platform building:

cd client
mkdir build && cd build
cmake ..
make

Dependencies

  • CMake 3.10+ - Build system

  • C++17 Compiler - GCC 7+, Clang 5+, or MSVC 2017+

  • pthread - Threading support (Linux/macOS)

  • Winsock2 - Network sockets (Windows)

  • nlohmann/json - JSON library (included in de_common)

  • DroneEngage communicator - Running on port 60000

API Reference

CModule Class

class CModule {
public:
    static CModule& getInstance();
    void defineModule(int moduleClass, const std::string& name, 
                     const std::string& id, const std::string& version, 
                     const Json_de& messageFilter);
    void addModuleFeatures(int feature);
    void setHardware(const std::string& hardwareId, int hardwareType);
    void setMessageOnReceive(void (*callback)(const char*, int, Json_de));
    void init(const std::string& serverIP, int serverPort, 
             const std::string& listenIP, int listenPort, int packetSize);
    void sendJMSG(const std::string& target, const Json_de& message, 
                  int messageType, bool requireAck);
    void sendBMSG(const std::string& target, const char* data, int length, 
                  int messageType, bool requireAck, const Json_de& metadata);
};

CUDPClient Class

class CUDPClient {
public:
    CUDPClient(CCallBack_UDPClient* callback);
    bool initialize(int port);
    void sendMessage(const std::vector<uint8_t>& data);
    void setJsonId(const std::string& jsonId);
};

CFacade_Base Class

class CFacade_Base {
public:
    virtual Json_de handleCommand(const Json_de& request) = 0;
    Json_de generateResponse(const std::string& status, const Json_de& data);
    void sendErrorMessage(const std::string& target, int partyId, 
                         int errorCode, int notificationType, 
                         const std::string& message);
};

Code Style

  • Variables: camelCase (e.g., moduleName, messageData)

  • Classes: PascalCase (e.g., CModule, CUDPClient)

  • Constants: UPPER_SNAKE_CASE (e.g., DEFAULT_PORT, MAX_PACKET_SIZE)

  • Files: snake_case (e.g., de_module.cpp, udp_client.h)

Performance Considerations

Memory Management

  • RAII principles for resource management

  • Smart pointers for automatic memory cleanup

  • Memory pools for frequent allocations

Thread Safety

  • Mutex protection for shared data

  • Lock-free queues for high-frequency operations

  • Atomic operations for simple state changes

Network Optimization

  • UDP buffer tuning for high throughput

  • Chunk size optimization for network conditions

  • Adaptive retransmission strategies

Debugging and Testing

Logging

Comprehensive logging system with multiple levels:

LOG_INFO("Module registered: %s", moduleName.c_str());
LOG_ERROR("Failed to send message: %s", errorMessage.c_str());
LOG_DEBUG("Received chunk %d/%d", chunkNum, totalChunks);

Unit Tests

Test coverage for:

  • Module registration and communication

  • Message chunking and reassembly

  • Binary data transmission

  • Error handling and edge cases

Additional Resources