C SDK
ESP32 Guide

ESP32 Guide

Send telemetry from an ESP32 to your Plexus dashboard over HTTPS. The C SDK has no external dependencies, runs in as low as ~1.5 KB of RAM, and integrates directly with ESP-IDF.

Prerequisites

Step 1: Add the SDK to Your Project

Copy the src/ directory and hal/esp32/ directory into your ESP-IDF project:

your-project/
├── main/
│   ├── main.c
│   └── CMakeLists.txt
├── components/
│   └── plexus/
│       ├── src/
│       │   ├── plexus.h
│       │   ├── plexus.c
│       │   ├── plexus.hpp
│       │   ├── plexus_config.h
│       │   ├── plexus_internal.h
│       │   └── plexus_json.c
│       ├── hal/
│       │   └── esp32/
│       │       ├── plexus_hal_esp32.c
│       │       └── plexus_hal_storage_esp32.c
│       └── CMakeLists.txt

Create components/plexus/CMakeLists.txt:

idf_component_register(
    SRCS "src/plexus.c" "src/plexus_json.c"
         "hal/esp32/plexus_hal_esp32.c"
         "hal/esp32/plexus_hal_storage_esp32.c"
    INCLUDE_DIRS "src"
    REQUIRES esp_http_client esp_timer nvs_flash
)

Step 2: Initialize and Send Data

Include plexus.h and use these core functions:

#include "plexus.h"
 
void app_main(void) {
    // ... WiFi setup omitted (see full example below) ...
 
    // Initialize a heap-allocated client
    plexus_client_t* px = plexus_init("plx_xxxxx", "esp32-001");
    if (!px) {
        ESP_LOGE("app", "Failed to init Plexus client");
        return;
    }
 
    // Queue metrics
    plexus_send(px, "temperature", 23.5);
    plexus_send(px, "humidity", 61.2);
 
    // Send queued metrics to Plexus
    plexus_err_t err = plexus_flush(px);
    if (err != PLEXUS_OK) {
        ESP_LOGE("app", "Flush failed: %s", plexus_strerror(err));
    }
 
    // Clean up
    plexus_free(px);
}

Sending Different Value Types

// Numbers (most common)
plexus_send(px, "temperature", 23.5);
plexus_send_number(px, "rpm", 3450.0);
 
// With explicit timestamp (Unix ms)
plexus_send_number_ts(px, "pressure", 1013.25, 1700000000000ULL);
 
// Strings (requires PLEXUS_ENABLE_STRING_VALUES=1, on by default)
plexus_send_string(px, "vehicle.state", "RUNNING");
 
// Booleans (requires PLEXUS_ENABLE_BOOL_VALUES=1, on by default)
plexus_send_bool(px, "motor.enabled", true);
 
// Numbers with tags (requires PLEXUS_ENABLE_TAGS=1, on by default)
const char* keys[]   = {"location", "unit"};
const char* values[] = {"lab-1",    "celsius"};
plexus_send_number_tagged(px, "temperature", 23.5, keys, values, 2);

Step 3: Use Auto-Flush with plexus_tick()

Instead of manually calling plexus_flush(), call plexus_tick() in your main loop. It handles time-based auto-flush (default: 5 seconds). Count-based auto-flush (default: 16 metrics) happens automatically in plexus_send_*():

while (1) {
    plexus_send(px, "temperature", read_temp());
    plexus_send(px, "humidity", read_humidity());
 
    // Handles time-based auto-flush
    plexus_err_t err = plexus_tick(px);
    if (err != PLEXUS_OK) {
        ESP_LOGE("app", "Tick error: %s", plexus_strerror(err));
    }
 
    vTaskDelay(pdMS_TO_TICKS(1000));
}

You can override the flush interval and count at runtime:

plexus_set_flush_interval(px, 10000);  // Flush every 10 seconds
plexus_set_flush_count(px, 8);         // Flush after 8 queued metrics

Step 4: Sessions

Group related data into sessions for analysis and playback:

plexus_session_start(px, "thermal-test-001");
 
for (int i = 0; i < 100; i++) {
    plexus_send(px, "temperature", read_temp());
    plexus_tick(px);
    vTaskDelay(pdMS_TO_TICKS(500));
}
 
plexus_session_end(px);

Check if a session is active:

const char* sid = plexus_session_id(px);
if (sid) {
    ESP_LOGI("app", "Active session: %s", sid);
}

Step 5: Error Handling

Every function that can fail returns plexus_err_t. Always check return values:

plexus_err_t err = plexus_send(px, "temperature", 23.5);
if (err == PLEXUS_ERR_BUFFER_FULL) {
    // Buffer full — flush first, then retry
    plexus_flush(px);
    plexus_send(px, "temperature", 23.5);
}

Use plexus_strerror() to get a human-readable message:

plexus_err_t err = plexus_flush(px);
if (err != PLEXUS_OK) {
    ESP_LOGE("app", "Error: %s", plexus_strerror(err));
}

Error Codes

CodeNameMeaning
0PLEXUS_OKSuccess
1PLEXUS_ERR_NULL_PTRNull pointer argument
2PLEXUS_ERR_BUFFER_FULLMetric buffer is full — flush first
3PLEXUS_ERR_STRING_TOO_LONGString exceeds max length
4PLEXUS_ERR_NO_DATANo data to flush
5PLEXUS_ERR_NETWORKNetwork or HTTP error
6PLEXUS_ERR_AUTHAuthentication failed (401) — check API key
7PLEXUS_ERR_FORBIDDENForbidden (403) — missing scope
8PLEXUS_ERR_BILLINGBilling limit exceeded (402)
9PLEXUS_ERR_RATE_LIMITRate limited (429) — back off
10PLEXUS_ERR_SERVERServer error (5xx)
11PLEXUS_ERR_JSONJSON serialization error
12PLEXUS_ERR_NOT_INITIALIZEDClient not initialized
13PLEXUS_ERR_HALHAL layer error
14PLEXUS_ERR_INVALID_ARGInvalid argument (bad characters, etc.)

Full Example

This is a complete ESP-IDF app_main that connects to WiFi, syncs time via NTP, and streams sensor data:

#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/event_groups.h"
#include "esp_wifi.h"
#include "esp_event.h"
#include "esp_log.h"
#include "nvs_flash.h"
#include "plexus.h"
 
#define WIFI_SSID      "YourWiFiSSID"
#define WIFI_PASSWORD  "YourWiFiPassword"
#define PLEXUS_API_KEY "plx_xxxxx"
#define SOURCE_ID      "esp32-sensor-001"
 
static const char* TAG = "app";
static EventGroupHandle_t s_wifi_event_group;
#define WIFI_CONNECTED_BIT BIT0
 
static void wifi_event_handler(void* arg, esp_event_base_t base,
                               int32_t id, void* data) {
    if (base == WIFI_EVENT && id == WIFI_EVENT_STA_START) {
        esp_wifi_connect();
    } else if (base == WIFI_EVENT && id == WIFI_EVENT_STA_DISCONNECTED) {
        esp_wifi_connect();
    } else if (base == IP_EVENT && id == IP_EVENT_STA_GOT_IP) {
        xEventGroupSetBits(s_wifi_event_group, WIFI_CONNECTED_BIT);
    }
}
 
static void wifi_init(void) {
    s_wifi_event_group = xEventGroupCreate();
    ESP_ERROR_CHECK(esp_netif_init());
    ESP_ERROR_CHECK(esp_event_loop_create_default());
    esp_netif_create_default_wifi_sta();
 
    wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
    ESP_ERROR_CHECK(esp_wifi_init(&cfg));
 
    ESP_ERROR_CHECK(esp_event_handler_instance_register(
        WIFI_EVENT, ESP_EVENT_ANY_ID, &wifi_event_handler, NULL, NULL));
    ESP_ERROR_CHECK(esp_event_handler_instance_register(
        IP_EVENT, IP_EVENT_STA_GOT_IP, &wifi_event_handler, NULL, NULL));
 
    wifi_config_t wifi_config = {
        .sta = { .ssid = WIFI_SSID, .password = WIFI_PASSWORD },
    };
    ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA));
    ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA, &wifi_config));
    ESP_ERROR_CHECK(esp_wifi_start());
 
    xEventGroupWaitBits(s_wifi_event_group, WIFI_CONNECTED_BIT,
                        pdFALSE, pdFALSE, portMAX_DELAY);
    ESP_LOGI(TAG, "WiFi connected");
}
 
void app_main(void) {
    ESP_ERROR_CHECK(nvs_flash_init());
    wifi_init();
 
    // Optional: sync time via NTP for accurate timestamps
    extern void plexus_hal_init_time(const char* ntp_server);
    plexus_hal_init_time("pool.ntp.org");
 
    // Initialize Plexus
    plexus_client_t* px = plexus_init(PLEXUS_API_KEY, SOURCE_ID);
    if (!px) {
        ESP_LOGE(TAG, "Failed to init Plexus");
        return;
    }
 
    plexus_set_flush_interval(px, 5000);
 
    ESP_LOGI(TAG, "Plexus SDK v%s, client size: %u bytes",
             plexus_version(), (unsigned)plexus_client_size());
 
    // Telemetry loop
    while (1) {
        plexus_send(px, "temperature", read_temperature());
        plexus_send(px, "humidity", read_humidity());
        plexus_send(px, "pressure", read_pressure());
 
        plexus_err_t err = plexus_tick(px);
        if (err != PLEXUS_OK) {
            ESP_LOGE(TAG, "Error: %s", plexus_strerror(err));
        }
 
        vTaskDelay(pdMS_TO_TICKS(1000));
    }
 
    plexus_free(px);
}

Next Steps