API Reference
The complete public surface of libscadable v0.1.0: three functions and one enum. The library is intentionally minimal — v0.1.0 ships the smallest demonstrable thing. Convenience layers (typed event publish, log forwarding, command handlers) accrete in v0.2.0+.
#include "scadable.h"scadable_user_main
scadable_user_mainvoid scadable_user_main(void);The library's entry hook into your code. The library calls this once after NVS + log initialization, before the network is up.
You define this; the library provides a weak no-op default. If you don't define it, the device boots into headless mode and idles (the library logs a warning).
Inside scadable_user_main:
- You're free to block forever, return, or spawn your own tasks.
esp_netif_init()andesp_event_loop_create_default()have already been called.- The library's bootstrap is dormant until something produces an IP. Bringing up Wi-Fi (or Ethernet, or cellular) from inside
scadable_user_mainis what triggers the rest of the bootstrap.
Example
void scadable_user_main(void) {
my_wifi_connect(); // your responsibility
while (1) {
do_application_work();
vTaskDelay(pdMS_TO_TICKS(1000));
}
}Advanced: owning app_main
app_mainIf you absolutely need lower-level control (e.g. peripheral init that must run before everything else), define your own app_main strongly:
void app_main(void) {
my_pre_bootstrap_init();
// ... the library's weak app_main is overridden ...
}You're then responsible for any SCADABLE bootstrap; the library no longer does it for you. Rarely needed.
scd_qos_t
scd_qos_ttypedef enum {
SCD_QOS_0 = 0, /* at most once — telemetry default */
SCD_QOS_1 = 1, /* at least once — important events */
SCD_QOS_2 = 2, /* exactly once — rarely needed */
} scd_qos_t;Standard MQTT QoS levels. The library's own heartbeat uses SCD_QOS_0; OTA status uses SCD_QOS_1.
scadable_mqtt_publish
scadable_mqtt_publishesp_err_t scadable_mqtt_publish(
const char *topic,
const void *payload,
int len,
scd_qos_t qos,
bool retain
);Publish raw bytes to a MQTT topic.
Parameters
| Param | Type | Description |
|---|---|---|
topic | const char * | Topic string. v0.1.0 has no generated topic constants yet — use literals. |
payload | const void * | Pointer to payload bytes (typically JSON). |
len | int | Length of payload in bytes. Pass -1 for null-terminated strings. |
qos | scd_qos_t | QoS level. |
retain | bool | Whether the broker should retain this message. |
Return values
| Value | Meaning |
|---|---|
ESP_OK | Successfully enqueued for transmission. |
ESP_ERR_INVALID_STATE | MQTT not connected. Gate with scadable_mqtt_connected(). |
ESP_ERR_INVALID_ARG | topic or payload is NULL. |
ESP_ERR_NO_MEM | MQTT client outbox full. |
Properties
- Thread-safe — call from any task.
- Non-blocking — returns immediately; transmission happens asynchronously.
- Zero heap allocation in the call path — payload is handed straight to
esp_mqtt_client_publish, which copies once into its outbox.
Example
char buf[64];
int n = snprintf(buf, sizeof(buf), "{\"v\":%.2f}", reading);
if (scadable_mqtt_connected()) {
scadable_mqtt_publish("my/custom/topic", buf, n, SCD_QOS_0, false);
}In v0.1.0 the library uses this function internally for its own heartbeat. You can call it for ad-hoc publishes; v0.2.0 adds typed helpers (scadable_publish_event) that wrap it with a metadata envelope and topic conventions.
scadable_mqtt_connected
scadable_mqtt_connectedbool scadable_mqtt_connected(void);Returns true once the MQTT session is up.
Use this to gate scadable_mqtt_publish calls if you want to avoid the ESP_ERR_INVALID_STATE return on disconnect, or to defer some domain work until the platform link is live.
Example
while (!scadable_mqtt_connected()) {
ESP_LOGI("app", "waiting for SCADABLE link");
vTaskDelay(pdMS_TO_TICKS(1000));
}
ESP_LOGI("app", "online; starting sensor loop");Topic conventions (v0.1.0)
The library uses three topics internally. You normally don't publish to these — they're the library's own channels — but knowing the convention helps when debugging with mosquitto_sub or reading dashboard data.
| Topic | Direction | Purpose | QoS |
|---|---|---|---|
scadable/<cn>/heartbeat | device → backend | Periodic device-health publish | 0 |
scadable/<cn>/ota/command | backend → device | OTA dispatch (subscribed by library) | (broker) |
scadable/<cn>/ota/status | device → backend | OTA progress + result | 1 |
<cn> is your device certificate's Common Name, format SC-<device_id>. The library assembles these topics at boot once the cert is loaded.
For ad-hoc publishes, pick any topic structure that makes sense for your data; the backend currently consumes the reserved topics above plus anything else routed through the broker's general subscription rules.
Heartbeat payload shape
Library publishes this every CONFIG_SCD_HEARTBEAT_INTERVAL_S seconds (default 30):
{
"uptime_s": 1234,
"free_heap": 187392,
"boot": 1,
"fw": "0.1.0"
}Fields are stable in v0.1.0; v0.2.0 may add more (RSSI, region, etc.) but won't remove existing ones.
OTA command payload (incoming)
Dashboard "Deploy" sends:
{
"version": "0.2.3",
"url": "https://app.scadable.com/api/firmware/<artifact>.bin"
}The library downloads via esp_https_ota, writes to the inactive OTA slot, restarts, and marks the new image valid on the first successful boot.
OTA status payload (outgoing)
Library publishes progress + result on scadable/<cn>/ota/status:
{
"version": "0.2.3",
"state": "downloading|progress|success|failed",
"details": "42% (256000/608000)"
}details is omitted for the initial downloading event.
Standalone build
The library has no codegen step in v0.1.0; everything is hand-written C with no preprocessor magic. No SCADABLE_NO_GENERATED flag exists yet. The library compiles cleanly into any ESP-IDF project ≥ 5.1.
Stability
The v0.1.0 surface is intentionally tiny and stable. v0.2.0 will add functions (scadable_publish_event, log forwarding helpers, command registration) without breaking v0.1.0 callers.
