ESP32 — getting started#
The TollanAgent library lets an ESP32 (or any Arduino board with WiFiClientSecure) be a Tollan device directly — no gateway box, no Go binary. It opens an outbound mutual-TLS WebSocket to the relay and speaks the same tollan.v1 protocol as the rest of the fleet, so the board is reachable at a public Tollan address with no inbound ports.
Internet ──▶ Tollan relay ──(mTLS WSS, tollan.v1)──▶ ESP32 (TollanAgent)
(public port / answers channels
SNI hostname) in your sketchNote
Status: first cut. The framing codec is complete and conformance-tested against the same byte-vectors as the Go agent and Node relay. The mTLS-WebSocket transport is implemented to spec and compiles for ESP32, but is still being validated end-to-end on hardware against a live relay. Read the TLS & clock notes before shipping.
What you'll need#
- An ESP32 board and the Arduino-ESP32 core (Arduino IDE,
arduino-cli, or PlatformIO). - A Tollan device bundle for the board (three PEM files — details below).
- A relay that accepts your board's TLS version — most ESP32 cores negotiate TLS 1.2, so the relay may need
RELAY_ALLOW_TLS12=true. See troubleshooting.
1. Install the library#
Tip
Download: tollan-agent-library.zip — the TollanAgent Arduino/PlatformIO library. Unzip it into your Arduino libraries/ folder, or point platformio.ini at the unzipped path.
Arduino IDE / CLI — copy the tollan-agent-library folder into your Arduino libraries/ directory (or arduino-cli lib install --git-url <repo>), then open File ▸ Examples ▸ TollanAgent ▸ BasicTunnel. Any ESP32 board works.
PlatformIO — reference the library in platformio.ini:
[env:esp32dev]
platform = espressif32
board = esp32dev
framework = arduino
lib_deps = symlink://../embedded/tollan-agent-library ; or a path / git URL
monitor_speed = 1152002. Provision the board (bundle mode)#
Two ways to get this board's identity onto the hardware: grab a complete, ready-to-flash project in one click, or wire the certificates into your own sketch by hand.
Download demo project — ready to flash#
On the device's install step in the console, an ESP32 device shows a Download demo project button next to Download locally. It downloads a complete PlatformIO project built for this device — everything already wired:
platformio.ini— build config (boardesp32dev, Arduino framework);src/main.cpp— the BasicTunnel sketch (the library's own maintained example, so it never drifts);src/tollan_config.h— this device's relay endpoint, CA, client certificate, and private key;src/ca.crt/device.crt/device.key— the same material as raw PEM files;lib/tollan-agent/— theTollanAgentlibrary vendored in, so the project builds offline with no separate library install.
Flash it in three steps:
- Unpack the archive and open the folder in VS Code with the PlatformIO extension (or run
pio run). PlatformIO installs the ESP32 toolchain on the first build. - Edit the two Wi-Fi lines at the top of
src/main.cpp—WIFI_SSID/WIFI_PASS. - Connect the board over USB and Upload (
pio run -t upload), then open the Serial Monitor at 115200 baud.
Because the project is prebuilt for the device, it already contains the library (step 1) and the example sketch (step 3) with the certificates baked in — so you can skip straight to flashing and ignore the manual library install and cert wiring below.
Important
The link is one-time and expires in 15 minutes, and — like every bundle build — generating it rotates the device's certificate, superseding any earlier bundle or demo project for the same device. Download once and flash a single artifact. The project carries a private key (src/tollan_config.h and src/device.key): keep it off shared drives and version control.
Or wire it up yourself#
Register the board as a device in the console exactly like any other. The architecture you pick is cosmetic here — you're taking the certificates, not the Go binary. Open the device's bundle and grab the three PEMs:
| Bundle file | Sketch constant | Purpose |
|---|---|---|
ca.crt | CA_CERT | CA that signed the relay's server cert — the board pins it |
device.crt | DEVICE_CERT | this board's client certificate (mTLS) |
device.key | DEVICE_KEY | this board's private key — keep secret, never commit |
An ESP32 bundle also ships a ready-to-include tollan_config.h with those constants plus TOLLAN_RELAY_HOST / TOLLAN_RELAY_PORT. Drop it next to your sketch and it's picked up automatically:
#if defined(__has_include) && __has_include("tollan_config.h")
# include "tollan_config.h" // shipped in the bundle — the happy path
#else
# warning "tollan_config.h not found — building with PLACEHOLDER certs."
// ...paste ca.crt / device.crt / device.key here as a fallback...
#endifThe #warning means a forgotten config fails loudly at build time instead of silently shipping placeholder certs.
Important
Set cfg.host to the relay's RELAY_PUBLIC_SNI — the exact name in the relay's server certificate, since the board pins it — and cfg.port to the relay control port (e.g. 7000).
3. Handler mode: the board is the service#
In the default handler mode, whatever hits the board's public Tollan address arrives in your sketch as channel DATA, and you reply on the same channel. Here's the minimal shape (see the full BasicTunnel example, which serves a live temperature reading as JSON):
#include <WiFi.h>
#include <TollanAgent.h>
using namespace tollan;
TollanAgent agent;
void setup() {
Serial.begin(115200);
WiFi.mode(WIFI_STA);
WiFi.begin("your-wifi", "your-wifi-password");
while (WiFi.status() != WL_CONNECTED) delay(300);
TollanConfig cfg;
cfg.host = TOLLAN_RELAY_HOST; // == RELAY_PUBLIC_SNI
cfg.port = TOLLAN_RELAY_PORT; // relay control-channel port, e.g. 7000
cfg.caCert = CA_CERT;
cfg.clientCert = DEVICE_CERT;
cfg.clientKey = DEVICE_KEY;
// Answer whatever the relay forwards on a channel.
agent.onData([](uint32_t ch, const uint8_t* data, size_t len) {
const char* body =
"HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\n"
"Content-Length: 3\r\nConnection: close\r\n\r\nhi\n";
agent.send(ch, (const uint8_t*)body, strlen(body));
agent.closeChannel(ch);
});
agent.begin(cfg);
}
void loop() {
agent.loop(); // drives connect, reconnect, keepalive, dispatch — call every iteration
delay(2);
}That's the whole integration: configure, register one callback, and call agent.loop() forever.
4. Add a route and test#
Back in the console, add a route to the device with an internal target of the board itself (handler mode doesn't dial anything, so any placeholder target is fine — the board answers directly). Then hit the public address:
curl https://your-board.tollan.app/The relay opens a channel to the board, your onData runs, and the reply streams back through the tunnel.
What agent.loop() does#
Calling loop() every iteration drives everything cooperatively:
- TLS + WebSocket connect and
tollan.v1subprotocol negotiation, - SNTP clock sync before the first TLS handshake (a fresh ESP32 boots near 1970, which breaks certificate date checks — see clock),
- reconnect with jittered exponential backoff,
tollankeepalivePONGs,- inbound frame dispatch to your callbacks.
A protocol-version mismatch is terminal — the agent stops and does not reconnect (TollanState::Terminal), mirroring the Go agent. Everything else is retried.
Next: proxy other hosts on the board's network with forwarding mode, or see the full API reference.