Skip to content

C SDK

libadamo exposes the Adamo SDK through a small C ABI (adamo.h). A header-only C++17 RAII wrapper (adamo.hpp) ships alongside.

The C SDK ships as a prebuilt tarball from install.adamohq.com/sdk/. Pick the build that matches your host OS, architecture, and whether you need video support, then extract it into a CMake-discoverable prefix (e.g. /opt).

Terminal window
# Linux arm64 (Jetson / Ubuntu 22.04 aarch64), with video:
curl -fsSL -o adamo-sdk.tar.gz \
https://install.adamohq.com/sdk/adamo-sdk-linux-arm64-video-latest.tar.gz
# Verify the checksum (recommended)
curl -fsSL https://install.adamohq.com/sdk/adamo-sdk-linux-arm64-video-latest.tar.gz.sha256 | shasum -a 256 -c
# Extract — the tarball expands to a top-level directory
sudo tar xzf adamo-sdk.tar.gz -C /opt

Available builds:

Tarball stemHost
adamo-sdk-linux-arm64-videoJetson / Ubuntu 22.04 aarch64, with video pipeline
adamo-sdk-linux-arm64Jetson / Ubuntu 22.04 aarch64, pub/sub only
adamo-sdk-linux-x86_64-videoUbuntu 22.04 x86_64, with video pipeline
adamo-sdk-linux-x86_64Ubuntu 22.04 x86_64, pub/sub only
adamo-sdk-macos-arm64macOS arm64, pub/sub only
adamo-sdk-macos-x86_64macOS x86_64, pub/sub only

Replace latest with a pinned version (e.g. 0.3.1) in production.

Then in your project’s CMakeLists.txt:

list(APPEND CMAKE_PREFIX_PATH "/opt/adamo-sdk-linux-arm64-video-latest")
find_package(Adamo REQUIRED)
target_link_libraries(my_robot PRIVATE Adamo::adamo)
# C++17 wrapper:
# target_link_libraries(my_robot PRIVATE Adamo::adamo_cpp)

Each tarball ships:

  • lib/libadamo.{so,dylib} — the shared library
  • include/adamo/adamo.h — the C header
  • include/adamo/adamo.hpp — the C++17 RAII wrapper (when ADAMO_BUILD_CPP=ON was set, which is the default)
  • lib/cmake/Adamo/ — CMake package config so find_package(Adamo) works
  • Every public symbol is prefixed adamo_.
  • Functions returning a handle (adamo_session_t *, etc.) return NULL on failure.
  • Functions returning int32_t use 0 for success and -1 for failure.
  • After any failure, call adamo_last_error() to retrieve a thread-local error string. Valid until the next fallible call on the same thread.
  • Every handle returned by the library is heap-allocated and must be freed with its matching adamo_*_free function.
  • All _free functions are NULL-safe.
  • The library manages its own internal runtime; C callers don’t see it. adamo_session_t * is safe to share between threads.

const char *adamo_last_error(void);

Returns the most recent error message for the current thread, or NULL if there is none. The pointer is valid until the next fallible call. Each thread has its own slot.


typedef enum adamo_protocol_t {
adamo_protocol_t_ADAMO_PROTOCOL_UDP = 0,
adamo_protocol_t_ADAMO_PROTOCOL_QUIC = 1,
adamo_protocol_t_ADAMO_PROTOCOL_TCP = 2,
} adamo_protocol_t;

QUIC is the recommended default — reliable streams over a single multiplexed UDP socket. UDP is the lowest-latency unreliable transport. TCP is a fallback for environments that block UDP/QUIC.


adamo_session_t *adamo_open_default(const char *api_key);
adamo_session_t *adamo_open(const char *api_key, adamo_protocol_t protocol);
void adamo_session_free(adamo_session_t *sess);
const char *adamo_session_org(const adamo_session_t *sess); // NUL-terminated
size_t adamo_session_org_len(const adamo_session_t *sess); // bytes, excluding NUL

adamo_open_default opens with the default transport. adamo_open is available when you need to select a specific transport. Both return NULL on failure. adamo_session_free is NULL-safe and closes the underlying connection if this was the last reference.

adamo_session_org returns a NUL-terminated C string valid for the lifetime of the session. adamo_session_org_len is provided for callers that prefer length-prefixed reads.


typedef struct adamo_sample_t {
char *key; // NUL-terminated, prefix-stripped
uint8_t *payload; // owned by the struct
uintptr_t payload_len;
int32_t is_delete; // 0 = PUT, 1 = DELETE (liveliness-gone)
} adamo_sample_t;
void adamo_sample_free(adamo_sample_t *sample); // NULL-safe

Samples returned from adamo_sub_recv / adamo_sub_try_recv / adamo_get / callback subscribers are heap-allocated and must be freed.


int32_t adamo_put(
const adamo_session_t *sess,
const char *key,
const uint8_t *payload,
uintptr_t payload_len,
uint8_t priority, // 0–255, mapped to 8 priority classes; ≥240 = REAL_TIME
int32_t express // 1 = bypass batching for lower latency
);

Returns 0 on success, -1 on failure.

adamo_publisher_t *adamo_publisher(
const adamo_session_t *sess,
const char *key,
uint8_t priority,
int32_t express,
int32_t reliable // 1 = RELIABLE, 0 = BEST_EFFORT
);
int32_t adamo_publisher_put(
const adamo_publisher_t *pub,
const uint8_t *payload,
uintptr_t payload_len
);
void adamo_publisher_free(adamo_publisher_t *pub);

adamo_publisher_free undeclares before freeing. NULL-safe.

int32_t adamo_log(
const adamo_session_t *sess,
const char *name, // robot name
const char *message,
const char *level // e.g. "info", "warn", "error", "debug" (may be NULL)
);

Publish a log line from the given robot. The web operator console subscribes to this stream and renders entries in real time.

level is a free-form string; the standard values "info", "warn", "error", "debug" are rendered with colour. Passing NULL for level defaults to "info". Messages are truncated at 10,000 characters. Each entry is stamped with the fabric clock so lines from multiple robots stay ordered in the operator view.

Returns 0 on success, -1 on failure.

adamo_log(sess, "my-robot", "booted and attached camera", "info");
adamo_log(sess, "my-robot", "encoder dropped a frame", "warn");

adamo_subscriber_t *adamo_subscribe(const adamo_session_t *sess, const char *key);
adamo_sample_t *adamo_sub_recv(const adamo_subscriber_t *sub, uint64_t timeout_ms);
adamo_sample_t *adamo_sub_try_recv(const adamo_subscriber_t *sub);
void adamo_sub_free(adamo_subscriber_t *sub);

timeout_ms = 0 waits forever. adamo_sub_recv returns NULL on timeout (no error set) or on real failure (adamo_last_error() populated). adamo_sub_try_recv returns NULL with no error when the queue is empty.

Wildcards in key:

PatternMatches
my-arm/sensors/**Everything under sensors/.
my-arm/sensors/*One level under sensors/.
*/sensors/imuIMU from any robot.

typedef void (*adamo_sample_cb_t)(const adamo_sample_t *sample, void *user);
adamo_cb_sub_t *adamo_subscribe_cb(
const adamo_session_t *sess,
const char *key,
adamo_sample_cb_t cb,
void *user
);
void adamo_cb_sub_free(adamo_cb_sub_t *sub);

The callback runs on a background receive thread. The sample pointer is borrowed — do not call adamo_sample_free from inside the callback. user must outlive the subscriber.


adamo_sample_t **adamo_get(
const adamo_session_t *sess,
const char *key,
uint64_t timeout_ms, // 0 → SDK default (60s)
size_t *count_out
);
void adamo_get_replies_free(adamo_sample_t **replies, size_t count);

Collects every reply within timeout_ms. On success returns an array of *count_out sample pointers. Returns NULL on failure (set *count_out to 0).

adamo_get_replies_free frees each sample in the array and the array storage. NULL-safe.


adamo_liveliness_token_t *adamo_liveliness_declare(
const adamo_session_t *sess,
const char *token_key
);
void adamo_liveliness_token_free(adamo_liveliness_token_t *tok);

Declare yourself alive at {token_key}/alive. The token is active until freed.

char **adamo_liveliness_get(
const adamo_session_t *sess,
const char *pattern, // e.g. "**/alive"
size_t *count_out
);
void adamo_liveliness_tokens_free(char **tokens, size_t count);

One-shot query for currently-live tokens. Returns an array of *count_out NUL-terminated strings (prefix-stripped). Free with adamo_liveliness_tokens_free, which frees each string and the array storage.

typedef void (*adamo_liveliness_cb_t)(const char *key, int32_t alive, void *user);
adamo_liveliness_sub_t *adamo_liveliness_subscribe(
const adamo_session_t *sess,
const char *pattern,
int32_t history, // 1 = deliver current set up front, 0 = changes only
adamo_liveliness_cb_t cb,
void *user
);
void adamo_liveliness_sub_free(adamo_liveliness_sub_t *sub);

Watch for liveliness changes. The callback fires with (key, 1) when a token appears and (key, 0) when it disappears. key is valid only for the duration of the call. Runs on a background receive thread; user must outlive the subscriber.


uint64_t adamo_fabric_now_us(void);
int32_t adamo_fabric_synced(void); // 1 once the first sync completes

Microseconds since the Unix epoch on the adamo fabric clock — the shared time axis every node on the network sees. Use these instead of gettimeofday() whenever a timestamp will be subtracted from a stamp produced on a different node.

Until the first sync, adamo_fabric_now_us falls back to the local wall clock.

These are free functions — they don’t take a session, so any code on the host can stamp timestamps consistently.


When the library is built with the video feature, the following are available:

adamo_robot_t *adamo_robot_new_default(
const char *api_key,
const char *name // may be NULL — random name assigned
);
adamo_robot_t *adamo_robot_new(
const char *api_key,
const char *name, // may be NULL — random name assigned
adamo_protocol_t protocol
);
void adamo_robot_free(adamo_robot_t *robot); // NULL-safe
int32_t adamo_robot_attach_video_v4l2(
adamo_robot_t *robot,
const char *name, // track name
const char *device, // "/dev/video0"
uint32_t width,
uint32_t height,
uint32_t fps,
uint32_t bitrate_kbps,
bool stereo
);

The SDK owns capture and encoding; frames never cross back into C. Returns 0 on success, -1 on failure.

adamo_video_track_t *adamo_robot_video(
adamo_robot_t *robot,
const char *name,
uint32_t width,
uint32_t height,
const char *pixel_format, // "BGRA", "RGB", "I420", "NV12", ...
uint32_t fps,
uint32_t bitrate_kbps,
bool stereo
);
int32_t adamo_video_track_send(
adamo_video_track_t *track,
const uint8_t *payload,
uintptr_t payload_len // must equal width × height × bytes-per-pixel
);
void adamo_video_track_free(adamo_video_track_t *track); // NULL-safe

Push raw frames yourself. Useful for perception pipelines and custom drivers.

int32_t adamo_robot_run(adamo_robot_t *robot); // blocks driving the pipeline

Blocks indefinitely. Call from a dedicated thread. Consumes the robot — multiple run calls on the same handle are an error.

const char *adamo_detect_encoder(void);

Returns the best available H.264 encoder name for the host (NVENC, VA-API, VideoToolbox, x264) or "none". Pointer valid for the lifetime of the process.


Handle / pointerOwnerFree function
adamo_session_t *C calleradamo_session_free
adamo_publisher_t *C calleradamo_publisher_free (undeclares first)
adamo_subscriber_t *C calleradamo_sub_free
adamo_cb_sub_t *C calleradamo_cb_sub_free
adamo_sample_t * (returned)C calleradamo_sample_free
adamo_sample_t * (in callback)borroweddo not free
adamo_sample_t ** array (from adamo_get)C calleradamo_get_replies_free
adamo_liveliness_token_t *C calleradamo_liveliness_token_free (undeclares first)
char ** array (from adamo_liveliness_get)C calleradamo_liveliness_tokens_free
adamo_liveliness_sub_t *C calleradamo_liveliness_sub_free
adamo_robot_t *C caller (until adamo_robot_run)adamo_robot_free
adamo_video_track_t *C calleradamo_video_track_free
const char * from adamo_session_org / adamo_last_error / adamo_detect_encoderborroweddo not free

A header-only wrapper providing move-only RAII handles and exception-based error propagation. Mirrors the C API one-to-one.

#include "adamo/adamo.hpp"
#include <iostream>
int main() {
auto sess = adamo::Session::open("ak_your_key");
sess.put("my-robot/sensors/temperature", "22.5");
auto sub = sess.subscribe("my-robot/sensors/**");
while (auto sample = sub.recv(/* timeout_ms */ 5000)) {
std::cout << sample->key() << ": "
<< sample->payload_len() << " bytes\n";
}
}
namespace adamo {
enum class Protocol : int { Quic, Udp, Tcp };
inline constexpr Protocol default_protocol = Protocol::Quic;
class Error : public std::runtime_error;
struct SampleView { // borrowed view (callbacks)
std::string_view key;
const uint8_t *payload;
std::size_t payload_len;
bool is_delete;
static SampleView from_c(const adamo_sample_t *s);
};
class Sample; // owned, move-only
class Publisher; // move-only RAII
class Subscriber; // move-only RAII
class CallbackSubscriber; // move-only RAII, owns the trampoline
class LivelinessToken; // move-only RAII
class LivelinessSubscriber; // move-only RAII, owns the trampoline
class Session; // move-only RAII
#ifdef ADAMO_HAS_VIDEO
class VideoTrack; // move-only RAII
class Robot; // move-only RAII
inline std::string_view detect_encoder();
#endif
}
class Session {
public:
static Session open(const std::string &api_key);
static Session open(const std::string &api_key,
Protocol p);
std::string_view org() const;
void put(const std::string &key,
const uint8_t *data, std::size_t len,
std::uint8_t priority = 4, bool express = false);
void put(const std::string &key, std::string_view s, ...);
Publisher publisher(const std::string &key,
std::uint8_t priority = 4,
bool express = false,
bool reliable = true);
Subscriber subscribe(const std::string &key);
template <class F>
CallbackSubscriber subscribe_cb(const std::string &key, F &&cb);
// F is callable as void(SampleView)
std::vector<Sample> get(const std::string &key,
std::uint64_t timeout_ms = 0);
LivelinessToken alive(const std::string &token_key);
std::vector<std::string> live_tokens(const std::string &pattern = "**/alive");
template <class F>
LivelinessSubscriber on_liveliness(const std::string &pattern,
F &&cb,
bool history = true);
// F is callable as void(std::string_view, bool)
};

All methods throw adamo::Error on failure.

class Sample {
public:
std::string_view key() const;
const uint8_t *payload() const noexcept;
std::size_t payload_len() const noexcept;
bool is_delete() const noexcept;
std::vector<uint8_t> payload_vec() const;
explicit operator bool() const noexcept;
};
class Publisher {
public:
void put(const uint8_t *data, std::size_t len);
void put(std::string_view s);
void put(const std::vector<uint8_t> &v);
};
class Subscriber {
public:
std::optional<Sample> recv(std::uint64_t timeout_ms = 0);
std::optional<Sample> try_recv();
};

Empty optional means timeout (recv) or empty queue (try_recv). Real errors throw.

Both are RAII move-only handles. LivelinessToken keeps the token alive until destruction; LivelinessSubscriber keeps the callback registered until destruction.

class Robot {
public:
static Robot create(const std::string &api_key,
std::optional<std::string> name);
static Robot create(const std::string &api_key,
std::optional<std::string> name,
Protocol p);
VideoTrack video(const std::string &name,
std::uint32_t width, std::uint32_t height,
const std::string &pixel_format,
std::uint32_t fps, std::uint32_t bitrate_kbps,
bool stereo = false);
void attach_v4l2(const std::string &name, const std::string &device,
std::uint32_t width, std::uint32_t height,
std::uint32_t fps, std::uint32_t bitrate_kbps,
bool stereo = false);
int run() &&; // consumes the Robot; returns adamo_robot_run's int
};
class VideoTrack {
public:
void send(const uint8_t *data, std::size_t len);
};

Define ADAMO_HAS_VIDEO in your build to enable these — the CMake build does this automatically when ADAMO_BUILD_VIDEO=ON.