OddSockets C++ SDK
Modern C++ SDK for embedded systems and IoT devices - JavaScript 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
# CMakeLists.txt
find_package(OddSockets REQUIRED)
target_link_libraries(your_target OddSockets::oddsockets)
vcpkg install oddsockets-cpp-sdk
# conanfile.txt
[requires]
oddsockets-cpp-sdk/1.0.0
[generators]
cmake
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
#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
#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)
#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
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
// 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
// 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
// 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
- Typing —
startTyping(userId, channel),stopTyping(userId, channel)→ broadcastuser_typing,user_stopped_typing - Reactions —
addReaction(messageId, channel, emoji, userId, userName),removeReaction(messageId, channel, emoji, userId)→ broadcastreaction_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. Ackchallenge_create_success.reportProgress(challengeId, value, ...)— fire-and-forget metric progress. No ack.completeChallenge(challengeId, outcome, ...)— finalize with an outcome. Ackchallenge_complete_success.unlockAchievement(achievementId, ..., percentComplete, ...)— fire-and-forget; passpercentComplete(0–100). No ack.getStandings(challengeId, limit, offset)— request top-N + caller rank. Ackchallenge_standings_success.getAchievements(achievementId)— query achievement state. Ackachievement_state.sendChallengeInvite(toUserId, ...)— directed invite to another user. Ackchallenge_invite_success.replyChallengeInvite(inviteId, accept, ...)— accept / decline an invite. Ackchallenge_reply_success.cancelChallengeInvite(inviteId)— cancel a sent invite. Ackchallenge_invite_cancel_success.getChallengeInvites()— list pending invites. Ackchallenge_invites.
Completion outcomes
The outcome argument to completeChallenge is one of:
completed— win (rank 1)failed— losstied— drawconceded— resign / concedeexpired— 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 broadcasts —
challenge_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,
0–1
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:
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)
#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
#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
#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
#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;
}