Thanks to visit codestin.com
Credit goes to docs.oddsockets.com

OddSockets C++ SDK

Modern C++ SDK for embedded systems and IoT devices - JavaScript Pattern Compliant

CMake Ready C++17 Embedded/IoT High Performance JS Pattern Compliant

Overview & Features

The OddSockets C++ SDK provides a powerful, resource-efficient interface for real-time messaging specifically designed for embedded systems and IoT devices while maintaining full compatibility with the JavaScript SDK pattern.

Embedded Optimized

Minimal memory footprint, configurable resource limits, and custom allocator support for embedded systems.

Modern C++17

Clean, type-safe API with RAII principles, smart pointers, and modern async programming.

JS Pattern Compliant

100% architectural compliance with JavaScript SDK - same API patterns and behavior.

High Performance

Optimized for low latency with efficient WebSocket connections and minimal overhead.

Cross-Platform

Works on Linux, Windows, macOS, and embedded platforms with consistent behavior.

Production Ready

Thread-safe, automatic failover, and comprehensive error handling for mission-critical applications.

Installation

cmake
# CMakeLists.txt
find_package(OddSockets REQUIRED)
target_link_libraries(your_target OddSockets::oddsockets)
bash
vcpkg install oddsockets-cpp-sdk
ini
# conanfile.txt
[requires]
oddsockets-cpp-sdk/1.0.0

[generators]
cmake
bash
git clone https://github.com/jyswee/oddsockets-cpp-sdk.git
cd oddsockets-cpp-sdk
mkdir build && cd build
cmake ..
make install

Quick Start

Basic Usage

cpp
#include 
#include 

int main() {
    // Create configuration
    oddsockets::Config config;
    config.apiKey = "ak_live_1234567890abcdef";
    config.userId = "user123";
    
    // Create client
    auto client = std::make_unique(config);
    
    // Connect to platform
    auto connectFuture = client->connect();
    bool connected = connectFuture.get();
    
    if (connected) {
        // Get a channel
        auto channel = client->channel("my-channel");
        
        // Subscribe to messages
        channel->subscribe([](const std::string& message) {
            std::cout << "Received: " << message << std::endl;
        });
        
        // Publish a message
        auto publishFuture = channel->publish("Hello from C++!");
        auto result = publishFuture.get();
        
        if (result.success) {
            std::cout << "Message published successfully!" << std::endl;
        }
    }
    
    return 0;
}

Embedded System Usage

cpp
#include 

// Custom allocator for embedded systems
class EmbeddedAllocator {
public:
    static void* allocate(size_t size) {
        return malloc(size);
    }
    
    static void deallocate(void* ptr) {
        free(ptr);
    }
};

int main() {
    // Set custom allocator
    oddsockets::setCustomAllocator();
    
    // Minimal configuration for embedded systems
    oddsockets::Config config;
    config.apiKey = "ak_live_1234567890abcdef";
    config.maxChannels = 4;           // Limit channels
    config.maxMessageSize = 1024;     // Smaller messages
    config.enableLogging = false;     // Disable logging
    config.enableSSL = false;         // Disable SSL if not needed
    config.enableThreading = false;   // Single-threaded mode
    
    auto client = std::make_unique(config);
    
    // Connect and use...
    auto connectFuture = client->connect();
    bool connected = connectFuture.get();
    
    if (connected) {
        auto channel = client->channel("iot-data");
        
        // Process events manually in single-threaded mode
        while (client->isConnected()) {
            client->processEvents();
            std::this_thread::sleep_for(std::chrono::milliseconds(10));
        }
    }
    
    return 0;
}

Async/Await Style (C++20)

cpp
#include 
#include 

// C++20 coroutine support (optional)
oddsockets::task chatExample() {
    oddsockets::Config config;
    config.apiKey = "ak_live_1234567890abcdef";
    
    auto client = std::make_unique(config);
    
    // Await connection
    bool connected = co_await client->connect();
    
    if (connected) {
        auto channel = client->channel("chat");
        
        // Await subscription
        bool subscribed = co_await channel->subscribe([](const std::string& msg) {
            std::cout << "Message: " << msg << std::endl;
        });
        
        if (subscribed) {
            // Await publish
            auto result = co_await channel->publish("Hello from C++20!");
            std::cout << "Published: " << result.success << std::endl;
        }
    }
}

Configuration

Client Configuration

cpp
oddsockets::Config config;

// Required
config.apiKey = "ak_live_1234567890abcdef";

// Optional
config.userId = "user123";
config.managerUrl = "https://connect.oddsockets.tyga.network";

// Connection Options
config.autoConnect = true;
config.reconnectAttempts = 5;
config.reconnectDelayMs = 1000;
config.connectionTimeoutMs = 10000;
config.messageTimeoutMs = 5000;

// Resource Limits (for embedded systems)
config.maxChannels = 32;
config.maxMessageSize = 32768;  // 32KB

// SSL/TLS Options
config.enableSsl = true;
config.sslVerifyPeer = true;
config.caCertPath = "/path/to/ca-cert.pem";

// Logging
config.logLevel = oddsockets::LogLevel::Info;
config.logCallback = [](oddsockets::LogLevel level, const std::string& message) {
    std::cout << "[" << oddsockets::logLevelToString(level) << "] " << message << std::endl;
};

// Callbacks
config.connectionCallback = [](oddsockets::ConnectionState state) {
    std::cout << "Connection: " << oddsockets::connectionStateToString(state) << std::endl;
};

config.errorCallback = [](oddsockets::ErrorCode error, const std::string& message) {
    std::cout << "Error: " << message << std::endl;
};

Channel Options

cpp
// Subscribe options
oddsockets::SubscribeOptions subscribeOpts;
subscribeOpts.maxHistory = 100;
subscribeOpts.retainHistory = true;
subscribeOpts.enablePresence = true;

channel->subscribe(callback, subscribeOpts);

// Publish options
oddsockets::PublishOptions publishOpts;
publishOpts.ttlSeconds = 3600;
publishOpts.metadata = R"({"priority": "high"})";
publishOpts.storeInHistory = true;

channel->publish("Hello, World!", publishOpts);

// History options
oddsockets::HistoryOptions historyOpts;
historyOpts.count = 50;
historyOpts.startTime = "2024-01-01T00:00:00Z";
historyOpts.endTime = "2024-01-02T00:00:00Z";

auto historyFuture = channel->getHistory(historyOpts);

Embedded Systems & IoT

The C++ SDK is specifically optimized for embedded systems and IoT devices with minimal resource requirements:

Memory Management

Custom allocators, memory tracking, and configurable limits for resource-constrained environments.

Single-Threaded Mode

Optional single-threaded operation for systems without threading support.

No-Exception Mode

Error code-based error handling for systems that don't support exceptions.

Configurable Features

Disable SSL, logging, or other features to reduce binary size and memory usage.

Memory Optimization

cpp
// Custom memory allocator
class PoolAllocator {
    static constexpr size_t POOL_SIZE = 64 * 1024; // 64KB pool
    static uint8_t pool[POOL_SIZE];
    static size_t offset;
    
public:
    static void* allocate(size_t size) {
        if (offset + size > POOL_SIZE) return nullptr;
        void* ptr = pool + offset;
        offset += size;
        return ptr;
    }
    
    static void deallocate(void* ptr) {
        // Pool allocator - no individual deallocation
    }
    
    static void reset() {
        offset = 0;
    }
};

// Set custom allocator
oddsockets::setCustomAllocator();

// Monitor memory usage
size_t allocated, peak;
oddsockets::getMemoryStats(&allocated, &peak);
std::cout << "Memory: " << allocated << " bytes (peak: " << peak << ")" << std::endl;

Minimal Configuration

cpp
// Minimal embedded configuration
oddsockets::Config config;
config.apiKey = "ak_live_1234567890abcdef";

// Resource limits
config.maxChannels = 2;           // Only 2 channels
config.maxMessageSize = 512;      // 512 byte messages
config.connectionTimeoutMs = 5000; // 5 second timeout

// Disable features
config.enableLogging = false;     // No logging
config.enableSSL = false;         // No SSL (if not needed)
config.enableThreading = false;   // Single-threaded
config.enableExceptions = false;  // No exceptions

// Minimal callbacks
config.errorCallback = [](oddsockets::ErrorCode error, const std::string&) {
    // Handle error codes instead of exceptions
    if (error != oddsockets::ErrorCode::Success) {
        // Handle error...
    }
};

Platform Support

Supported Platforms

  • Linux (ARM, x86, x64)
  • FreeRTOS
  • Zephyr RTOS
  • Arduino (ESP32, ESP8266)
  • Raspberry Pi
  • STM32
  • Nordic nRF
  • Windows IoT

Requirements

  • C++17 compiler
  • 32KB+ RAM (configurable)
  • TCP/IP stack
  • Optional: SSL/TLS support
  • Optional: Threading support

Examples

Explore comprehensive examples demonstrating the OddSockets C++ SDK in action:

Enhanced Features

Enhanced (Slack-like) events layer on top of the core pub/sub. The send side lives on client.enhanced() — each call is fire-and-forget (void) and emits a real worker event over the live socket. The matching broadcast arrives on the raw client.on("<event>", ...) surface as a JSON payload string, so any subscriber in the channel room can react.

Typing & reactions

#include <oddsockets/OddSockets.hpp>
#include <iostream>

oddsockets::Config config;
config.apiKey = "YOUR_API_KEY";
config.userId = "alice";
config.autoConnect = false;

oddsockets::OddSockets client(config);
client.connect().get();

auto channel = client.channel("room-42");
channel->subscribe([](const std::string&) {}).get();

// Receive-path: enhanced broadcasts arrive on the raw on() surface as JSON strings
client.on("user_typing",    [](const std::string& payload) { std::cout << "typing: "   << payload << "\n"; });
client.on("reaction_added", [](const std::string& payload) { std::cout << "reaction: " << payload << "\n"; });

// Send-path: fire enhanced actions over the live socket
client.enhanced().startTyping("alice", "room-42");
client.enhanced().addReaction("msg-1", "room-42", ":thumbsup:", "alice", "Alice");

Event surface

  • TypingstartTyping(userId, channel), stopTyping(userId, channel) → broadcast user_typing, user_stopped_typing
  • ReactionsaddReaction(messageId, channel, emoji, userId, userName), removeReaction(messageId, channel, emoji, userId) → broadcast reaction_added, reaction_removed

The C++ enhanced surface is deliberately focused on typing and reactions. Any other worker event your channel emits is still available directly on client.on("<event>", ...).

Challenges & Leaderboards

Challenges, leaderboards and achievements build on the same live socket. The send side lives on the enhanced surface (client.enhanced()); request/query methods resolve with the worker's reply, while progress and achievement calls are fire-and-forget. Inbound broadcasts arrive on the client event surface — subscribe with the client's normal client.on("<event>", ...).

Run a challenge

auto& enhanced = client.enhanced();

// Ack lands on the client event surface as a JSON payload string
client.on("challenge_standings_success", [](const std::string& payload) { std::cout << "standings: " << payload << "\n"; });

enhanced.createChallenge("weekly-kills", "kills");
enhanced.reportProgress("weekly-kills", 3);          // fire-and-forget
enhanced.getStandings("weekly-kills", 10);           // ack: challenge_standings_success
enhanced.completeChallenge("weekly-kills", "completed");

Methods

  • createChallenge(challengeId, metric, ...) — create a challenge / leaderboard. Ack challenge_create_success.
  • reportProgress(challengeId, value, ...) — fire-and-forget metric progress. No ack.
  • completeChallenge(challengeId, outcome, ...) — finalize with an outcome. Ack challenge_complete_success.
  • unlockAchievement(achievementId, ..., percentComplete, ...) — fire-and-forget; pass percentComplete (0–100). No ack.
  • getStandings(challengeId, limit, offset) — request top-N + caller rank. Ack challenge_standings_success.
  • getAchievements(achievementId) — query achievement state. Ack achievement_state.
  • sendChallengeInvite(toUserId, ...) — directed invite to another user. Ack challenge_invite_success.
  • replyChallengeInvite(inviteId, accept, ...) — accept / decline an invite. Ack challenge_reply_success.
  • cancelChallengeInvite(inviteId) — cancel a sent invite. Ack challenge_invite_cancel_success.
  • getChallengeInvites() — list pending invites. Ack challenge_invites.

Completion outcomes

The outcome argument to completeChallenge is one of:

  • completed — win (rank 1)
  • failed — loss
  • tied — draw
  • conceded — resign / concede
  • expired — timed out

Progressive achievements

unlockAchievement always emits the wire event achievement_unlock; the worker is authoritative and derives the outbound broadcast from percentComplete. A value < 100 broadcasts achievement_progress (status in_progress); a value >= 100 or omitted broadcasts achievement_unlock (status unlocked). Just call unlockAchievement with the right percentComplete — the worker chooses the broadcast for you.

Inbound events

Subscribe to these on the client's client.on("<event>", ...) surface as JSON payload strings.

  • Room broadcastschallenge_progress, leaderboard_rank_change, challenge_complete, achievement_unlock, achievement_progress
  • Directed (per-user)challenge_invited, challenge_reply_received, challenge_invite_cancelled

Usage Analytics

Pull your tenant's headline usage tiles — monthly active users, daily active users, total messages published, and error rate — straight from the SDK, without hand-rolling a REST call. getUsageStats() resolves the same four tiles the developer dashboard renders.

UsageStats stats = client.getUsageStats().get();

// Each tile is a std::optional: std::nullopt means "not live yet", never a
// real zero. Render an em-dash for a missing tile.
auto dash = [](auto opt) -> std::string {
    return opt ? std::to_string(*opt) : "\u2014";
};
std::cout << "MAU:      " << dash(stats.mau) << "\n";
std::cout << "DAU:      " << dash(stats.dau) << "\n";
std::cout << "Messages: " << dash(stats.totalMessages) << "\n";
std::cout << "Errors:   " << (stats.errorRate ? std::to_string(*stats.errorRate) : "\u2014") << "\n";

The four tiles

  • mau — monthly active users for your owner scope
  • dau — daily active users
  • totalMessages — total messages published
  • errorRate — publish error rate, 01

Honesty rule — std::nullopt, never a fake zero

Each tile is a std::optional — a value or std::nullopt. A std::nullopt means that leg of the analytics pipeline is not live yet for your tenant — the SDK preserves it verbatim and never substitutes 0 for a missing tile. Render an em-dash () for an empty optional so you never show a fabricated zero.

Requires an API key

getUsageStats() reads your owner-scoped analytics, so it needs an apiKey. Keyless / token-only clients have no owner scope to query, so the returned future throws getUsageStats requires an apiKey.

Performance & Compatibility

OddSockets C++ SDK delivers superior performance optimized for embedded systems:

<10ms
Latency
99.9%
Uptime
32KB
Min RAM
100K+
Messages/sec

Compiler Support

  • GCC 7+ (C++17)
  • Clang 6+ (C++17)
  • MSVC 2019+ (C++17)
  • ARM GCC (embedded)

Build Systems

  • CMake 3.12+
  • vcpkg
  • Conan
  • Arduino IDE

Platform Integrations

The OddSockets C++ SDK works seamlessly across multiple platforms and environments:

Arduino (ESP32)

cpp
#include 
#include 

const char* ssid = "your-wifi-ssid";
const char* password = "your-wifi-password";

std::unique_ptr client;

void setup() {
    Serial.begin(115200);
    
    // Connect to WiFi
    WiFi.begin(ssid, password);
    while (WiFi.status() != WL_CONNECTED) {
        delay(1000);
        Serial.println("Connecting to WiFi...");
    }
    
    // Configure OddSockets for ESP32
    oddsockets::Config config;
    config.apiKey = "ak_live_1234567890abcdef";
    config.maxChannels = 2;
    config.maxMessageSize = 1024;
    config.enableLogging = false;
    
    client = std::make_unique(config);
    
    // Connect and subscribe
    auto connectFuture = client->connect();
    if (connectFuture.get()) {
        auto channel = client->channel("sensor-data");
        
        channel->subscribe([](const std::string& message) {
            Serial.println(("Received: " + message).c_str());
        });
    }
}

void loop() {
    // Process events
    client->processEvents();
    
    // Send sensor data every 10 seconds
    static unsigned long lastSend = 0;
    if (millis() - lastSend > 10000) {
        auto channel = client->channel("sensor-data");
        
        String sensorData = "{\"temperature\": " + String(random(20, 30)) + 
                           ", \"humidity\": " + String(random(40, 60)) + "}";
        
        channel->publish(sensorData.c_str());
        lastSend = millis();
    }
    
    delay(10);
}

Raspberry Pi

cpp
#include 
#include 
#include 
#include 

const int LED_PIN = 18;
const int BUTTON_PIN = 24;

int main() {
    // Initialize WiringPi
    wiringPiSetupGpio();
    pinMode(LED_PIN, OUTPUT);
    pinMode(BUTTON_PIN, INPUT);
    pullUpDnControl(BUTTON_PIN, PUD_UP);
    
    // Configure OddSockets
    oddsockets::Config config;
    config.apiKey = "ak_live_1234567890abcdef";
    config.userId = "raspberry-pi-001";
    
    auto client = std::make_unique(config);
    
    // Connect
    auto connectFuture = client->connect();
    if (!connectFuture.get()) {
        std::cerr << "Failed to connect!" << std::endl;
        return 1;
    }
    
    auto controlChannel = client->channel("device-control");
    auto statusChannel = client->channel("device-status");
    
    // Subscribe to control commands
    controlChannel->subscribe([](const std::string& message) {
        std::cout << "Control command: " << message << std::endl;
        
        if (message == "LED_ON") {
            digitalWrite(LED_PIN, HIGH);
        } else if (message == "LED_OFF") {
            digitalWrite(LED_PIN, LOW);
        }
    });
    
    // Monitor button and send status
    bool lastButtonState = HIGH;
    
    while (client->isConnected()) {
        client->processEvents();
        
        // Check button state
        bool buttonState = digitalRead(BUTTON_PIN);
        if (buttonState != lastButtonState && buttonState == LOW) {
            statusChannel->publish("BUTTON_PRESSED");
            std::cout << "Button pressed!" << std::endl;
        }
        lastButtonState = buttonState;
        
        std::this_thread::sleep_for(std::chrono::milliseconds(50));
    }
    
    return 0;
}

FreeRTOS

cpp
#include "FreeRTOS.h"
#include "task.h"
#include "queue.h"
#include 

// FreeRTOS allocator
class FreeRTOSAllocator {
public:
    static void* allocate(size_t size) {
        return pvPortMalloc(size);
    }
    
    static void deallocate(void* ptr) {
        vPortFree(ptr);
    }
};

std::unique_ptr client;
QueueHandle_t messageQueue;

void oddSocketsTask(void* parameters) {
    // Set FreeRTOS allocator
    oddsockets::setCustomAllocator();
    
    // Configure for FreeRTOS
    oddsockets::Config config;
    config.apiKey = "ak_live_1234567890abcdef";
    config.maxChannels = 1;
    config.maxMessageSize = 256;
    config.enableLogging = false;
    config.enableThreading = false;  // Use FreeRTOS tasks instead
    
    client = std::make_unique(config);
    
    // Connect
    auto connectFuture = client->connect();
    if (connectFuture.get()) {
        auto channel = client->channel("freertos-device");
        
        channel->subscribe([](const std::string& message) {
            // Send message to queue for processing by other tasks
            const char* msg = message.c_str();
            xQueueSend(messageQueue, &msg, portMAX_DELAY);
        });
        
        // Main event loop
        while (1) {
            client->processEvents();
            vTaskDelay(pdMS_TO_TICKS(10));
        }
    }
    
    vTaskDelete(NULL);
}

void sensorTask(void* parameters) {
    while (1) {
        // Read sensor data
        float temperature = readTemperatureSensor();
        float humidity = readHumiditySensor();
        
        // Create JSON message
        char buffer[128];
        snprintf(buffer, sizeof(buffer), 
                "{\"temp\": %.2f, \"humidity\": %.2f}", 
                temperature, humidity);
        
        // Publish sensor data
        if (client && client->isConnected()) {
            auto channel = client->channel("freertos-device");
            channel->publish(std::string(buffer));
        }
        
        vTaskDelay(pdMS_TO_TICKS(30000)); // 30 seconds
    }
}

int main() {
    // Create message queue
    messageQueue = xQueueCreate(10, sizeof(char*));
    
    // Create tasks
    xTaskCreate(oddSocketsTask, "OddSockets", 4096, NULL, 2, NULL);
    xTaskCreate(sensorTask, "Sensor", 2048, NULL, 1, NULL);
    
    // Start scheduler
    vTaskStartScheduler();
    
    return 0;
}

Cross-Platform Desktop

cpp
#include 
#include 
#include 
#include 

#ifdef _WIN32
    #include 
#else
    #include 
#endif

class CrossPlatformApp {
private:
    std::unique_ptr client;
    std::atomic running{true};
    
public:
    void run() {
        // Configure client
        oddsockets::Config config;
        config.apiKey = "ak_live_1234567890abcdef";
        config.userId = getSystemId();
        
        // Platform-specific optimizations
        #ifdef __EMBEDDED__
            config.maxChannels = 2;
            config.enableLogging = false;
        #else
            config.maxChannels = 10;
            config.enableLogging = true;
        #endif
        
        client = std::make_unique(config);
        
        // Connect
        auto connectFuture = client->connect();
        if (!connectFuture.get()) {
            std::cerr << "Failed to connect!" << std::endl;
            return;
        }
        
        std::cout << "Connected to OddSockets!" << std::endl;
        
        // Setup channels
        auto chatChannel = client->channel("cross-platform-chat");
        auto statusChannel = client->channel("system-status");
        
        // Subscribe to chat
        chatChannel->subscribe([this](const std::string& message) {
            std::cout << "Chat: " << message << std::endl;
        });
        
        // Send periodic status updates
        std::thread statusThread([this, statusChannel]() {
            while (running) {
                std::string status = getSystemStatus();
                statusChannel->publish(status);
                
                std::this_thread::sleep_for(std::chrono::seconds(60));
            }
        });
        
        // Main loop
        std::string input;
        while (running && std::getline(std::cin, input)) {
            if (input == "/quit") {
                running = false;
                break;
            }
            
            chatChannel->publish(input);
        }
        
        statusThread.join();
        client->disconnect();
    }
    
private:
    std::string getSystemId() {
        #ifdef _WIN32
            char computerName[MAX_COMPUTERNAME_LENGTH + 1];
            DWORD size = sizeof(computerName);
            GetComputerNameA(computerName, &size);
            return std::string(computerName);
        #else
            char hostname[256];
            gethostname(hostname, sizeof(hostname));
            return std::string(hostname);
        #endif
    }
    
    std::string getSystemStatus() {
        // Platform-specific system info
        return R"({"platform": ")" + getPlatformName() + R"(", "uptime": )" + 
               std::to_string(getUptime()) + "}";
    }
    
    std::string getPlatformName() {
        #ifdef _WIN32
            return "Windows";
        #elif __linux__
            return "Linux";
        #elif __APPLE__
            return "macOS";
        #else
            return "Unknown";
        #endif
    }
    
    long getUptime() {
        #ifdef _WIN32
            return GetTickCount64() / 1000;
        #else
            return time(nullptr);
        #endif
    }
};

int main() {
    CrossPlatformApp app;
    app.run();
    return 0;
}