ESP32 — API reference#
The public surface of the TollanAgent library. Everything lives in the tollan namespace; include <TollanAgent.h>.
#include <TollanAgent.h>
using namespace tollan;class TollanAgent#
The agent object. Create one globally, configure it in setup(), and drive it from loop().
void begin(const TollanConfig& cfg);
void loop();
// Handler-mode replies
bool send(uint32_t channel, const uint8_t* data, size_t len);
bool closeChannel(uint32_t channel);
// Handler-mode callbacks
void onOpen(OpenHandler h);
void onData(DataHandler h);
void onClose(CloseHandler h);
// State
TollanState state() const;
bool isConnected() const; // state() == Connected
bool clockReady() const; // system clock is SNTP-synced (TLS dates can pass)begin(cfg)#
Stores the configuration and prepares the agent. Call once in setup() after Wi-Fi is up. Does not block on the network — the actual connect happens in loop().
loop()#
Drives the agent cooperatively — call it every loop() iteration. It handles, in order as needed: SNTP clock sync, TLS + WebSocket connect, tollan.v1 subprotocol negotiation, jittered exponential-backoff reconnect, keepalive PONGs, and inbound frame dispatch. It never blocks for long; a short delay(2) after it is fine.
send(channel, data, len) → bool#
Handler mode. Write len bytes back to the visitor on channel as a DATA frame. Returns false if the tunnel isn't up or the write fails. Call as many times as you like to stream a response.
closeChannel(channel) → bool#
Handler mode. Send a CLOSE on channel to signal end-of-reply. Returns false if the tunnel isn't up.
Callbacks#
Register handlers for inbound frames (handler mode only; ignored when cfg.forward is true):
using OpenHandler = std::function<void(uint32_t channel, const char* target, size_t targetLen)>;
using DataHandler = std::function<void(uint32_t channel, const uint8_t* data, size_t len)>;
using CloseHandler = std::function<void(uint32_t channel)>;onOpen— a new channel was opened.targetis the relay's requestedhost:port(may be empty); a self-serving endpoint can ignore it.onData—lenbytes arrived onchannel. This is the raw bytes of the tunneled connection (e.g. an HTTP request). Reply withsend()/closeChannel().onClose— the channel was closed; clean up any per-channel state.
State accessors#
TollanState s = agent.state();
if (agent.isConnected()) { /* tunnel up */ }
if (agent.clockReady()) { /* SNTP-synced; TLS date checks can pass */ }clockReady() is exposed so a sketch can show status or wait explicitly; connect() already gates on it internally when cfg.syncClock is set.
enum class TollanState#
enum class TollanState {
Disconnected, // idle / waiting out reconnect backoff
Connected, // the tunnel is up
Terminal, // fatal (e.g. protocol-version mismatch) — will NOT reconnect
};Terminal is reached only on an unrecoverable condition such as a tollan.v1 version mismatch. The agent deliberately stops rather than reconnect-loop against a relay it can't talk to. Everything else (network drops, TLS hiccups) resolves back through Disconnected → Connected on backoff.
struct TollanConfig#
Passed to begin(). Fields with defaults may be omitted.
Connection#
| Field | Type | Default | Purpose |
|---|---|---|---|
host | const char* | nullptr | Relay hostname to dial — also the TLS SNI the relay's cert is pinned to (RELAY_PUBLIC_SNI) |
port | uint16_t | 0 | Relay control-channel port, e.g. 7000 |
path | const char* | TOLLAN_TUNNEL_PATH | WebSocket upgrade path |
caCert | const char* | nullptr | PEM: CA that signed the relay's server cert |
clientCert | const char* | nullptr | PEM: this device's client certificate |
clientKey | const char* | nullptr | PEM: this device's private key |
Reconnect & framing#
| Field | Type | Default | Purpose |
|---|---|---|---|
minBackoffMs | uint32_t | 1000 | Reconnect backoff floor |
maxBackoffMs | uint32_t | 30000 | Reconnect backoff ceiling |
maxFrameBytes | uint16_t | 1600 | Per-frame payload cap (RAM guard) |
Clock sync (SNTP)#
mTLS verifies the relay cert's validity dates; a board with no RTC boots near 1970, so every current cert looks "not yet valid" (mbedTLS -9984). connect() syncs time over SNTP before the first TLS handshake.
| Field | Type | Default | Purpose |
|---|---|---|---|
syncClock | bool | true | Sync the clock before first TLS. Set false if the board already keeps real time (RTC, or your own configTime()) |
ntpServer1 | const char* | "pool.ntp.org" | Primary NTP server |
ntpServer2 | const char* | "time.nist.gov" | Secondary NTP server (use a LAN NTP if you have one) |
clockSyncTimeoutMs | uint32_t | 8000 | Max wait for the first NTP reply |
Forwarding mode#
See forwarding. Ignored unless forward is true.
| Field | Type | Default | Purpose |
|---|---|---|---|
forward | bool | false | Enable LAN-proxy mode (dial targets, pipe bytes) |
defaultTarget | const char* | nullptr | Target for an OPEN with no explicit target; always allowlisted |
forwardTargets | const char* const* | nullptr | Additional allowed host:port targets |
forwardTargetCount | size_t | 0 | Number of entries in forwardTargets |
forwardAllowAny | bool | false | Dial any relay-named target (drops the allowlist protection) |
localConnectTimeoutMs | uint32_t | 4000 | TCP dial timeout to the LAN host |
localReadChunk | uint16_t | 256 | Max LAN→relay bytes per channel per loop() |
Compile-time macros#
Define before including TollanAgent.h to override:
| Macro | Default | Purpose |
|---|---|---|
TOLLAN_MAX_FWD_CHANNELS | 6 | Max concurrent forwarded connections (each uses one lwIP socket) |
TOLLAN_FWD_BUF | 2048 | Per-channel relay→local staging buffer, bytes (≥ maxFrameBytes) |
#define TOLLAN_MAX_FWD_CHANNELS 4
#define TOLLAN_FWD_BUF 4096
#include <TollanAgent.h>Portable helpers#
Two headers are dependency-free and host-testable (no Arduino deps), and are what keep the C++ port byte-compatible with the Go and Node sides:
TollanProtocol.h— thetollan.v1framing codec (Frame, encode/decode). Verified against the same conformance vectors as the Go agent and the Node relay.TollanTargets.h—host:portparsing and allowlist membership used by forwarding mode.
You rarely call these directly, but they're the reason the wire protocol stays consistent across all three languages. Run their host tests with:
cd embedded/tollan-agent-library/test/native && make
# -> test_protocol: PASS
# -> test_targets: PASS