Skip to content
Console

Embedded C Library

Use the embedded C library when your client is a microcontroller or small device, such as ESP32, Zephyr, bare-metal, or a Linux single-board computer.

This is a protocol library, not a full SDK. It is transport-agnostic: you bring your own MQTT client or WebSocket client and your own TLS stack, and the library provides everything protocol-specific. It is pure C99 with zero external dependencies, and the core paths never allocate; every function writes into caller-provided buffers.

Source and releases: thalovant-embedded-c on GitHub.

Identity parsing

Parse the identity JSON issued by the Thalovant API, with the same field aliases the Node and Go SDKs accept.

MQTT topics

Derive per-client MQTT topics, connection endpoints, ports, and client IDs, byte-for-byte identical to the Node SDK.

Payload encryption

Self-contained AES-128-GCM with constant-time tag verification, validated against NIST vectors.

Wire framing

Build and parse hub wire frames and encrypted envelopes, plus helpers for the ask request and reply loop.

You provide The library provides
MQTT and/or WebSocket client Topics, endpoints, frame and envelope codecs
TLS stack TLS flag plus scheme and port parsing
Random number generator Nothing; nonces are always caller-supplied
Event loop and timers Frame classifier and ask-loop semantics
Identity JSON storage Identity parser

The library has no control-plane client. Create the client identity through the dashboard, the API, or another SDK, then store the identity JSON on the device.

Vendor the library or fetch it by an immutable release tag. The current tag is v0.1.0.

Terminal window
git submodule add https://github.com/thalovant/thalovant-embedded-c.git \
third_party/thalovant-embedded-c
git -C third_party/thalovant-embedded-c checkout v0.1.0

CMake FetchContent, ESP-IDF component references, and Zephyr west manifests work the same way with the v0.1.0 tag. Every GitHub release also carries a reproducible source archive, a CycloneDX SBOM, and a SHA256SUMS file, attested with GitHub Actions provenance.

Building needs only a C99 compiler:

Terminal window
make # build/libthalovant.a
make test # host-side, offline test suite

This MQTT sketch parses an identity, derives topics and the payload key, sends one utterance, and classifies the replies. Your MQTT client owns the connection.

#include "thalovant/thalovant.h"
thalovant_identity identity;
thalovant_identity_parse(identity_json, identity_len, &identity);
thalovant_mqtt_topics topics;
thalovant_mqtt_topics_derive(&identity, &topics);
uint8_t key[16];
thalovant_crypto_runtime_key(identity.crypto_key, key);
/* connect your MQTT client to the derived endpoint... */
/* subscribe topics.outbound; publish "online" retained on topics.status */
/* send an utterance */
char frame[1024];
thalovant_ask_request ask = { "what time is it", "en-us",
"sess-1", identity.site_id, "req-1" };
thalovant_ask_build_frame(&ask, frame, sizeof(frame));
uint8_t nonce[16]; /* fill from your RNG — never reuse */
uint8_t sealed[1100];
size_t sealed_len;
thalovant_envelope_encrypt_binary(key, nonce, (uint8_t *)frame,
strlen(frame), sealed, sizeof(sealed),
&sealed_len);
/* publish sealed on topics.inbound ... */
/* classify replies arriving on topics.outbound */
thalovant_ask_event event;
thalovant_ask_classify(plaintext, plaintext_len, "req-1", &event);
if (event.kind == THALOVANT_ASK_SPEAK) { /* speak event.text */ }

The repository includes full walkthroughs for ESP32 with MQTT and for Linux with a WebSocket client in its docs/ directory.

Use Python, Node.js, Go, or Rust when the device can run a full runtime. Those SDKs own the transport, reconnect logic, and control-plane calls for you. Use MQTT for broker details and topic scope.

Symptom Check
Identity parse fails Confirm the stored JSON is the untouched identity payload from the API or dashboard download.
Broker rejects the connection Confirm the derived endpoint, port, and TLS flag, and use the per-client broker credentials from the identity.
Replies never classify Confirm the subscribe topic is topics.outbound and the request ID matches the one sent in the ask frame.
Decrypt fails Confirm the payload key comes from thalovant_crypto_runtime_key and the nonce is read from the received envelope.