tollan docs

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.

text
  Internet ──▶ Tollan relay ──(mTLS WSS, tollan.v1)──▶ ESP32 (TollanAgent)
                (public port /                            answers channels
                 SNI hostname)                            in your sketch

Note

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#

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:

ini
[env:esp32dev]
platform = espressif32
board = esp32dev
framework = arduino
lib_deps = symlink://../embedded/tollan-agent-library   ; or a path / git URL
monitor_speed = 115200

2. Provision the board (bundle mode)#

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 fileSketch constantPurpose
ca.crtCA_CERTCA that signed the relay's server cert — the board pins it
device.crtDEVICE_CERTthis board's client certificate (mTLS)
device.keyDEVICE_KEYthis 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:

cpp
#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...
#endif

The #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):

cpp
#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:

bash
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.v1 subprotocol 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,
  • tollan keepalive PONGs,
  • 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.