SCADABLE
Platform feature

Upload device logs on the cadence your fleet actually needs

Stream logs, stack traces, and reboot reasons off every device. Tune the upload frequency in .scadable/config.toml. Buffered offline, flushed on reconnect.

ESP32 remote loggingembedded device log aggregationIoT log managementMemfault alternativeoffline log buffering

Logs you can actually read

Embedded debugging usually means a serial cable, a desk lamp, and a printout taped to the wall. The moment a device leaves your bench, those logs disappear. libscadable holds a 128-record ring buffer in RAM and a flusher task that drains it into one MQTT batch on a cadence you set. Your printf calls show up in a browser, filterable, and feed every other diagnostic surface in the platform.

Crash? You get the stack trace, register state, and last 32KB of log buffer. Reboot? You see the reason. Power-on, watchdog, brownout, software-triggered.

Set the upload frequency in .scadable/config.toml

Most devices want 5-second batched flushes. A solar field station that comes online once an hour wants the buffer to grow until the radio wakes, then drain in one push. A bench unit you are debugging wants every line in real time. You set this per project in your firmware repo, not by recompiling.

When MQTT is down, libscadable keeps appending to the ring buffer. When the broker reconnects the next flush drains everything that accumulated. This is the offline-tolerant path Verdant-style scheduled-online devices need. Without it, anything logged between online windows disappears.

.scadable/config.toml

toml
# Lives in your firmware repo. Picked up by libscadable at build time.
[logging]
# Flush the in-RAM buffer every N seconds.
# 0 disables batching (every line publishes immediately) — chatty but useful for bench debugging.
# 5 is the default — 5s batched flushes, ~30 records per envelope at INFO.
# 3600 for hourly cadence on Starlink-style scheduled-online devices.
batch_secs = 5

# Cap the in-RAM buffer. When full, the flusher wakes early instead of dropping records.
# Worst-case heap = ring_capacity * 256B (record size). Default 128 ≈ 32 KB.
ring_capacity = 128

# Minimum level to upload. Lines below this stay on the device serial only.
# debug | info | warn | error
upload_level = "info"

A few lines of C

c
#include "scadable.h"

SCADABLE_LOG_INFO("boot: firmware=%s rev=%d", FW_VERSION, HW_REV);
SCADABLE_LOG_WARN("battery low: %dmV", millivolts);
SCADABLE_LOG_ERROR("mqtt connect failed: %d", err);

What it captures

  • Log lines uploaded over MQTT, batched per your config.toml cadence
  • Stack traces with symbol resolution against your firmware artifacts
  • Reboot reasons (POR, BOR, WDT, software, deep-sleep wakeup)
  • Device metadata: firmware version, hardware revision, RSSI, battery
  • Offline buffering: ring fills while MQTT is down, drains on reconnect
  • Indexed and queryable as the input to remote diagnostics and OTA rollback

Memfault alternative

Teams comparing SCADABLE to Memfault: yes, we cover the same ground for crash reporting and structured logs, with two differences. First, logs are first-class. Not an add-on. Second, the same library that handles logs handles MQTT, certificates, OTA, and diagnostics. One integration, not five.

Bring your fleet onto SCADABLE.

Connect a repo, leave with a fleet you can manage from your codebase.

Let’s talk