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
- ESP-IDF v5.0 or later
- A Plexus API key from app.plexus.company (opens in a new tab)
- WiFi network access from your ESP32
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.txtCreate 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 metricsStep 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
| Code | Name | Meaning |
|---|---|---|
| 0 | PLEXUS_OK | Success |
| 1 | PLEXUS_ERR_NULL_PTR | Null pointer argument |
| 2 | PLEXUS_ERR_BUFFER_FULL | Metric buffer is full — flush first |
| 3 | PLEXUS_ERR_STRING_TOO_LONG | String exceeds max length |
| 4 | PLEXUS_ERR_NO_DATA | No data to flush |
| 5 | PLEXUS_ERR_NETWORK | Network or HTTP error |
| 6 | PLEXUS_ERR_AUTH | Authentication failed (401) — check API key |
| 7 | PLEXUS_ERR_FORBIDDEN | Forbidden (403) — missing scope |
| 8 | PLEXUS_ERR_BILLING | Billing limit exceeded (402) |
| 9 | PLEXUS_ERR_RATE_LIMIT | Rate limited (429) — back off |
| 10 | PLEXUS_ERR_SERVER | Server error (5xx) |
| 11 | PLEXUS_ERR_JSON | JSON serialization error |
| 12 | PLEXUS_ERR_NOT_INITIALIZED | Client not initialized |
| 13 | PLEXUS_ERR_HAL | HAL layer error |
| 14 | PLEXUS_ERR_INVALID_ARG | Invalid 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
- API Reference -- Full function and macro reference
- Arduino Guide -- Use the C SDK with Arduino
- STM32 Guide -- Use the C SDK with STM32 and FreeRTOS
- Memory Configuration -- Tune buffer sizes for your hardware