Send telemetry from ESP32, STM32, and Arduino to Plexus in 3 lines of code.
plexus_client_t* px = plexus_init("plx_your_api_key", "esp32-001");
plexus_send(px, "temperature", 72.5);
plexus_flush(px);~1.5KB RAM minimal, ~5KB default | Zero dependencies | Retry + backoff + rate-limit built in
Go to app.plexus.company → Add Device → copy the API key.
#include "plexus.h"
void app_main(void) {
// After WiFi is connected:
plexus_client_t* px = plexus_init("plx_your_api_key", "esp32-001");
plexus_send(px, "temperature", 72.5);
plexus_send(px, "humidity", 45.0);
plexus_flush(px);
plexus_free(px);
}#include <WiFi.h>
#include "plexus.hpp"
PlexusClient px("plx_your_api_key", "arduino-001");
void setup() {
WiFi.begin("SSID", "password");
while (WiFi.status() != WL_CONNECTED) delay(500);
}
void loop() {
px.send("temperature", analogRead(34) * 0.1);
px.send("humidity", analogRead(35) * 0.2);
px.tick(); // auto-flushes every 5 seconds
delay(1000);
}#include "plexus.h"
PLEXUS_CLIENT_STATIC_BUF(px_buf); // no malloc
void telemetry_task(void const* arg) {
plexus_client_t* px = plexus_init_static(
&px_buf, sizeof(px_buf), "plx_your_api_key", "stm32-001");
plexus_set_endpoint(px, "http://app.plexus.company/api/ingest");
for (;;) {
plexus_send(px, "temperature", read_temp());
plexus_tick(px);
osDelay(1000);
}
}Data appears in real time at app.plexus.company.
Copy src/ and the appropriate hal/ directory to your project.
// Heap-allocated (uses malloc)
plexus_client_t* px = plexus_init("plx_key", "source-id");
plexus_free(px);
// Static allocation (no malloc — MISRA C compliant)
PLEXUS_CLIENT_STATIC_BUF(buf);
plexus_client_t* px = plexus_init_static(&buf, sizeof(buf), "plx_key", "source-id");
// No free needed — just stop using it
// plexus_free() is safe on both: it only calls free() on heap-allocated clients.source_id must be URL-safe: only a-z A-Z 0-9 . _ - are allowed (no spaces). Use the slug shown in the Plexus dashboard, not the display name. plexus_init returns NULL if the source ID contains invalid characters.
plexus_send(px, "temperature", 72.5); // numeric (most common)
plexus_send_number_ts(px, "temp", 72.5, ts_ms); // with explicit timestamp
plexus_send_string(px, "status", "running"); // string (opt-in)
plexus_send_bool(px, "armed", true); // boolean (opt-in)
// With tags
const char* keys[] = {"location"};
const char* vals[] = {"room-1"};
plexus_send_number_tagged(px, "temp", 72.5, keys, vals, 1);plexus_flush(px); // send now (blocks during retries, max ~14s)
plexus_tick(px); // call from loop — auto-flushes on interval
plexus_pending_count(px); // queued metrics
plexus_clear(px); // discard queueplexus_set_endpoint(px, "https://custom.domain/api/ingest");
plexus_set_flush_interval(px, 10000); // auto-flush every 10s
plexus_set_flush_count(px, 8); // auto-flush after 8 metricsplexus_err_t err = plexus_flush(px);
if (err != PLEXUS_OK) {
printf("Error: %s\n", plexus_strerror(err));
}| Code | Meaning |
|---|---|
PLEXUS_OK |
Success |
PLEXUS_ERR_BUFFER_FULL |
Metric buffer full — flush or increase PLEXUS_MAX_METRICS |
PLEXUS_ERR_NETWORK |
Connection failed — metrics stay in buffer for retry |
PLEXUS_ERR_AUTH |
Bad API key (401) — no retry |
PLEXUS_ERR_FORBIDDEN |
Missing write scope (403) — no retry |
PLEXUS_ERR_BILLING |
Billing limit exceeded (402) — no retry |
PLEXUS_ERR_RATE_LIMIT |
Throttled (429) — auto-cooldown, retries after 30s |
PLEXUS_ERR_SERVER |
Server error (5xx) — retried with backoff |
Override via compiler flags (-DPLEXUS_MAX_METRICS=8) or before including plexus.h:
| Option | Default | Description |
|---|---|---|
PLEXUS_MAX_METRICS |
32 | Max metrics per flush |
PLEXUS_JSON_BUFFER_SIZE |
2048 | JSON serialization buffer |
PLEXUS_MAX_RETRIES |
3 | Retry count on failure |
PLEXUS_AUTO_FLUSH_COUNT |
16 | Auto-flush after N metrics |
PLEXUS_AUTO_FLUSH_INTERVAL_MS |
5000 | Auto-flush interval (0=disabled) |
PLEXUS_ENABLE_TAGS |
1 | Metric tags support |
PLEXUS_ENABLE_STRING_VALUES |
1 | String value support |
PLEXUS_ENABLE_BOOL_VALUES |
1 | Boolean value support |
PLEXUS_ENABLE_PERSISTENT_BUFFER |
0 | Flash-backed buffer for unsent data |
PLEXUS_ENABLE_STATUS_CALLBACK |
0 | Connection status notifications |
PLEXUS_ENABLE_THREAD_SAFE |
0 | Mutex-protected client access |
PLEXUS_DEBUG |
0 | Debug logging |
-DPLEXUS_MAX_METRICS=8
-DPLEXUS_JSON_BUFFER_SIZE=512
-DPLEXUS_ENABLE_TAGS=0
-DPLEXUS_ENABLE_STRING_VALUES=0
-DPLEXUS_ENABLE_BOOL_VALUES=0RAM usage is dominated by three components:
| Component | Default | Minimal | Scales with |
|---|---|---|---|
| Metrics buffer | largest | smallest | PLEXUS_MAX_METRICS × (name + value + timestamp + tags) |
| JSON buffer | 2048 B | 512 B | PLEXUS_JSON_BUFFER_SIZE |
| Fixed fields | ~512 B | ~320 B | API key, source ID, endpoint, session, state |
| Config | Total RAM per client |
|---|---|
| Default (all features, 32 metrics) | ~5 KB |
| Minimal (numbers only, 8 metrics) | ~1.5 KB |
Disabling tags (PLEXUS_ENABLE_TAGS=0) and string values (PLEXUS_ENABLE_STRING_VALUES=0) shrinks each metric slot significantly — the value union drops from 128 bytes to 8 bytes.
Get the exact size for your build:
printf("Client size: %zu bytes\n", sizeof(plexus_client_t));
| Platform | TLS | Timestamps | Notes |
|---|---|---|---|
| ESP32 (ESP-IDF) | HTTPS | NTP via SNTP | Full support, production-ready |
| ESP32/ESP8266 (Arduino) | HTTPS (no cert verify) | gettimeofday |
Add setCACert() for production |
| STM32 (LwIP) | HTTP only | RTC | Requires mbedTLS for HTTPS — see hal/stm32 header |
The STM32 HAL defaults to huart2 (debug UART) and hrtc (RTC). Override for your board:
-DPLEXUS_STM32_DEBUG_UART=huart3
-DPLEXUS_STM32_RTC=hrtc1
The STM32 HAL ships with plain HTTP. For production, add HTTPS via mbedTLS + LwIP's altcp_tls layer:
1. Enable mbedTLS in STM32CubeMX → Middleware → mbedTLS. Enable at minimum: MBEDTLS_SSL_CLI_C, MBEDTLS_SSL_TLS_C, MBEDTLS_NET_C.
2. Add to lwipopts.h:
#define LWIP_ALTCP 1
#define LWIP_ALTCP_TLS 1
#define LWIP_ALTCP_TLS_MBEDTLS 1
3. Replace socket calls in plexus_hal_stm32.c with altcp equivalents — altcp_new, altcp_connect, altcp_write, altcp_output. See the altcp_tls section in the LwIP docs.
4. Load root CA certificate for app.plexus.company to verify the server.
Alternatively, use a TLS-terminating reverse proxy (e.g., nginx) on your network edge and keep the HAL as-is with HTTP.
Unsent telemetry survives reboots when enabled:
-DPLEXUS_ENABLE_PERSISTENT_BUFFER=1On flush failure, the serialized batch is written to flash with CRC32 integrity check. On next plexus_flush(), persisted data is sent first. ESP32 uses NVS; STM32/Arduino require implementing 3 HAL storage functions.
Uses a ring buffer with PLEXUS_PERSIST_MAX_BATCHES slots (default 8). Oldest batches are overwritten when the ring buffer is full.
Not thread-safe by default. Confine all calls to a given client to a single thread/task.
- One client per task (default): No flag needed. Each client has its own buffers and no global state, so separate clients in separate tasks are always safe.
- Shared client across tasks: Enable
-DPLEXUS_ENABLE_THREAD_SAFE=1. This wraps all API calls in a platform mutex (FreeRTOSosMutexon STM32/ESP32,xSemaphoreCreateRecursiveMutexon ESP-IDF). The calling task blocks if another task holds the lock. plexus_init_static()with a shared buffer: The buffer must not be accessed by multiple tasks duringplexus_init_static(). After initialization, access is governed by the thread-safe flag above.
plexus_flush() blocks while sending. Timing depends on the outcome:
| Scenario | Duration |
|---|---|
| Success (first attempt) | < 1 second |
| Transient failure + retry | 1–5 seconds |
| All retries fail (worst case) | ~14 seconds |
The worst case is 3 retries with exponential backoff (500ms → 1s → 2s → ... up to 8s max, plus ±25% jitter). On FreeRTOS, the calling task yields during delays. On bare-metal Arduino, delay() blocks everything.
For non-blocking operation, use plexus_tick() from your main loop — it only flushes when the auto-flush interval or count threshold is reached, and returns immediately otherwise.
Copy hal/template/plexus_hal_template.c and implement the HAL functions. See the verification checklist at the bottom of the template.
| Plexus C SDK | Raw MQTT | ThingsBoard SDK | AWS IoT SDK | |
|---|---|---|---|---|
| RAM | ~1.5KB min | ~10KB+ | ~50KB+ | ~30KB+ |
| Dependencies | None | MQTT lib | MQTT + cJSON | mbedTLS + MQTT |
| Auth | API key header | Broker creds | Device token | mTLS certs |
| Protocol | HTTP(S) | MQTT persistent | MQTT persistent | MQTT + mTLS |
| Persistent buffer | Built-in | Manual | Manual | Manual |
| Setup | 3 lines of code | Broker + subscriber + DB + UI | Self-host or cloud | IoT Core + provisioning |
Apache License 2.0 — see LICENSE.
- Issues: https://github.com/plexus-oss/c-sdk/issues
- Discord: https://discord.gg/plexus