> ## Documentation Index
> Fetch the complete documentation index at: https://docs.pumplink.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# ESP32 quickstart

> Connect an ESP32 to PumpLink in under 30 minutes using PlatformIO

This guide walks you through connecting an ESP32 to PumpLink. By the end, your device will appear in the dashboard and respond to activation commands.

## What you need

* An ESP32 board (any variant — ESP32-WROOM, ESP32-S3, etc.)
* A relay module wired to your ESP32 (or just an LED for testing)
* [PlatformIO](https://platformio.org/install/ide?install=vscode) installed in VS Code
* A PumpLink account — [sign up free](https://app.pumplink.dev/register)

***

## Step 1 — Register your device

Sign in to the [PumpLink dashboard](https://app.pumplink.dev), go to **Devices → Add device**, give it a name, and click **Register**.

You'll see a **Device ID** and a **Password** shown once. Copy both immediately — the password is never shown again. If you lose it, you'll need to regenerate credentials and reflash.

<Warning>
  The password is shown exactly once. Store it securely before closing the dialog.
</Warning>

***

## Step 2 — Create your project

Create a new folder and add the following files:

### `platformio.ini`

```ini theme={null}
[env:esp32]
platform  = espressif32
board     = esp32dev
framework = arduino

lib_deps =
  knolleary/PubSubClient @ ^2.8
  bblanchon/ArduinoJson @ ^6.21.0

monitor_speed = 115200
```

Change `board` to match your specific board if needed (e.g. `esp32-s3-devkitc-1`).

***

### `src/config.h`

```cpp theme={null}
#pragma once

// ── WiFi ──────────────────────────────────────────────
#define WIFI_SSID      "YourNetworkName"
#define WIFI_PASSWORD  "YourNetworkPassword"

// ── PumpLink device credentials ───────────────────────
// Copy these from the PumpLink dashboard when you register a device.
// The password is shown only once — store it securely.
#define MQTT_USERNAME  "42"           // your numeric device ID
#define MQTT_PASSWORD  "your_token_here"

// ── Hardware ──────────────────────────────────────────
#define RELAY_PIN 26   // GPIO pin connected to your relay IN pin
                       // Change this to match your wiring
```

***

### `src/ca_cert.h`

This is the Let's Encrypt root CA certificate. It allows the ESP32 to verify
the TLS connection to `mqtt.pumplink.dev` without disabling certificate
verification.

```cpp theme={null}
#pragma once

// ISRG Root X1 — Let's Encrypt root certificate authority
// mqtt.pumplink.dev's TLS certificate is signed by this CA.
// This certificate expires 2035-06-04 and does not need to be updated
// when PumpLink renews its broker certificate.
const char* ROOT_CA = R"EOF(
-----BEGIN CERTIFICATE-----
MIIFazCCA1OgAwIBAgIRAIIQz7DSQONZRGPgu2OCiwAwDQYJKoZIhvcNAQELBQAw
TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh
cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMTUwNjA0MTEwNDM4
WhcNMzUwNjA0MTEwNDM4WjBPMQswCQYDVQQGEwJVUzEpMCcGA1UEChMgSW50ZXJu
ZXQgU2VjdXJpdHkgUmVzZWFyY2ggR3JvdXAxFTATBgNVBAMTDElTUkcgUm9vdCBY
MTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoBggIBAK3oJHP0FDfzm54rVygc
h77ct984kIxuPOZXoHj3dcKi/vVqbvYATyjb3miGbESTtrFj/RQSa78f0uoxmyF+
0TM8ukj13Xnfs7j/EvEhmkvBioZxaUpmZmyPfjxwv60pIgbz5MDmgK7iS4+3mX6U
A5/TR5d8mUgjU+g4rk8Kb4Mu0UlXjIB0ttov0DiNewNwIRt18jA8+o+u3dpjq+sW
T8KOEUt+zwvo/7V3LvSye0rgTBIlDHCNAymg4VMk7BPZ7hm/ELNKjD+Jo2FR3qyH
B5T0Y3HsLuJvW5iB4YlcNHlsdu87kGJ55tukmi8mxdAQ4Q7e2RCOFvu396j3x+UC
B5iPNgiV5+I3lg02dZ77DnKxHZu8A/lJBdiB3QW0KtZB6awBdpUKD9jf1b0SHzUv
KBds0pjBqAlkd25HN7rOrFleaJ1/ctaJxQZBKT5ZPt0m9STJEadao0xAH0ahmbWn
OlFuhjuefXKnEgV4We0+UXgVCwOPjdAvBbI+e0ocS3MFEvzG6uBQE3xDk3SzynTn
jh8BCNAw1FtxNrQHusEwMFxIt4I7mKZ9YIqioymCzLq9gwQbooMDQaHWBfEbwrbw
qHyGO0aoSCqI3Haadr8faqU9GY/rOPNk3sgrDQoo//fb4hVC1CLQJ13hef4Y53CI
rU7m2Ys6xt0nUW7/vGT1M0NPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNV
HRMBAf8EBTADAQH/MB0GA1UdDgQWBBR5tFnme7bl5AFzgAiIyBpY9umbbjANBgkq
hkiG9w0BAQsFAAOCAgEAVR9YqbyyqFDQDLHYGmkgJykIrGF1XIpu+ILlaS/V9lZL
ubhzEFnTIZd+50xx+7LSYK05qAvqFyFWhfFQDlnrzuBZ6brJFe+GnY+EgPbk6ZGQ
3BebYhtF8GaV0nxvwuo77x/Py9auJ/GpsMiu/X1+mvoiBOv/2X/qkSsisRcOj/KK
NFtY2PwByVS5uCbMiogziUwthDyC3+6WVwW6LLv3xLfHTjuCvjHIInNzktHCgKQ5
ORAzI4JMPJ+GslWYHb4phowim57iaztXOoJwTdwJx4nLCgdNbOhdjsnvzqvHu7Ur
TkXWStAmzOVyyghqpZXjFaH3pO3JLF+l+/+sKAIuvtd7u+Nxe5AW0wdeRlN8NwdC
jNPElpzVmbUq4JUagEiuTDkHzsxHpFKVK7q4+63SM1N95R1NbdWhscdCb+ZAJzVc
oyi3B43njTOQ5yOf+1CceWxG1bQVs5ZufpsMljq4Ui0/1lvh+wjChP4kqKOJ2qxq
4RgqsahDYVvTH9w7jXbyLeiNdd8XM2w9U/t7y0Ff/9yi0GE44Za4rF2LN9d11TPA
mRGunUHBcnWEvgJBQl9nJEiU0Zsnvgc/ubhPgXRR4Xq37Z0j4r7g1SgEEzwxA57d
emyPxgcYxn/eR44/KJ4EBs+lVDR3veyJm+kXQ99b21/+jh5Xos1AnX5iItreGCc=
-----END CERTIFICATE-----
)EOF";
```

<Note>
  The CA certificate above verifies `mqtt.pumplink.dev`'s TLS certificate chain.
  You do not need to update this when PumpLink renews its broker certificate —
  the root CA is valid until 2035.
</Note>

***

### `src/main.cpp`

```cpp theme={null}
#include <Arduino.h>
#include <WiFi.h>
#include <WiFiClientSecure.h>
#include <PubSubClient.h>
#include "config.h"
#include "ca_cert.h"

// ── Broker ────────────────────────────────────────────
#define MQTT_HOST   "mqtt.pumplink.dev"
#define MQTT_PORT   41102

// ── Topic helpers ─────────────────────────────────────
String topicCommand() { return "device/" + String(MQTT_USERNAME) + "/command"; }
String topicAck()     { return "device/" + String(MQTT_USERNAME) + "/ack"; }
String topicStatus()  { return "device/" + String(MQTT_USERNAME) + "/status"; }

// ── Globals ───────────────────────────────────────────
WiFiClientSecure espClient;
PubSubClient     mqtt(espClient);

// ── MQTT callback ─────────────────────────────────────
void onMessage(char* topic, byte* payload, unsigned int length) {
  String msg;
  for (unsigned int i = 0; i < length; i++) msg += (char)payload[i];
  Serial.println("[MQTT] command: " + msg);

  if (msg == "on") {
    digitalWrite(RELAY_PIN, HIGH);
    Serial.println("[RELAY] ON");
    // Acknowledge the activation — any publish counts
    mqtt.publish(topicAck().c_str(), "ack", false);
    Serial.println("[MQTT] ack sent");
  }

  if (msg == "off") {
    digitalWrite(RELAY_PIN, LOW);
    Serial.println("[RELAY] OFF");
    // No ack required for off
  }
}

// ── WiFi ──────────────────────────────────────────────
void connectWiFi() {
  Serial.print("[WiFi] connecting to " + String(WIFI_SSID));
  WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  Serial.println("\n[WiFi] connected — IP: " + WiFi.localIP().toString());
}

// ── MQTT connect ──────────────────────────────────────
void connectMQTT() {
  while (!mqtt.connected()) {
    Serial.print("[MQTT] connecting...");
    if (mqtt.connect(
          ("esp32-" + String((uint32_t)ESP.getEfuseMac())).c_str(),
          MQTT_USERNAME,
          MQTT_PASSWORD)) {
      Serial.println(" connected");
      // Subscribe at QoS 1 — PubSubClient doesn't implement the QoS 2
      // PUBREC/PUBREL/PUBCOMP handshake, so the broker downgrades delivery
      // to QoS 1 for this subscription regardless of what it publishes at.
      mqtt.subscribe(topicCommand().c_str(), 1);
      Serial.println("[MQTT] subscribed to " + topicCommand());
    } else {
      Serial.printf(" failed (state=%d), retry in 5s\n", mqtt.state());
      delay(5000);
    }
  }
}

// ── Setup ─────────────────────────────────────────────
void setup() {
  Serial.begin(115200);
  pinMode(RELAY_PIN, OUTPUT);
  digitalWrite(RELAY_PIN, LOW);

  connectWiFi();

  // Load root CA — enables proper TLS certificate verification
  espClient.setCACert(ROOT_CA);

  mqtt.setServer(MQTT_HOST, MQTT_PORT);
  mqtt.setCallback(onMessage);
  mqtt.setKeepAlive(60);

  connectMQTT();
}

// ── Loop ──────────────────────────────────────────────
void loop() {
  if (!mqtt.connected()) connectMQTT();
  mqtt.loop();
}
```

***

## Step 3 — Flash

Open the project in VS Code with PlatformIO, then click **Upload** or run:

```bash theme={null}
pio run --target upload
```

Open the serial monitor (`pio device monitor`) and you should see:

```
[WiFi] connecting to YourNetworkName........
[WiFi] connected — IP: 192.168.1.42
[MQTT] connecting... connected
[MQTT] subscribed to device/42/command
```

***

## Step 4 — Verify

Go to the [PumpLink dashboard](https://app.pumplink.dev) — your device should appear as **Online**.

To trigger an activation without writing any client code, connect to the broker with [MQTT Explorer](https://mqtt-explorer.com) using your device's username and password (same host and port as your firmware — `mqtt.pumplink.dev:41102`, TLS enabled). Then:

1. Subscribe to `device/42/#` (replace `42` with your device ID) to watch all traffic for your device.
2. Publish `on` to `device/42/command`.

Your relay should click on immediately, and the device should publish to `device/42/ack` within 10 seconds. Publish `off` to the same command topic to turn it back off.

The serial monitor will show:

```
[MQTT] command: on
[RELAY] ON
[MQTT] ack sent
[MQTT] command: off
[RELAY] OFF
```

<Note>
  This manual publish is just for testing. In production, the backend publishes `on`/`off` to the command topic on your behalf when a user triggers an activation from the dashboard or API — see the [device contract](/devices/mqtt-contract) for the full handshake.
</Note>

***

## Troubleshooting

**Device doesn't appear in dashboard**

* Check serial monitor for connection errors
* Verify `MQTT_USERNAME` is your numeric device ID (e.g. `"42"` not `"device_42"`)
* Verify `MQTT_PASSWORD` is correct — regenerate from dashboard if unsure

**TLS handshake fails**

* Confirm `mqtt.pumplink.dev` resolves from your network
* The CA cert in `ca_cert.h` must be complete and unmodified

**Relay doesn't click**

* Verify `RELAY_PIN` matches your wiring
* Most relay modules are active HIGH — `digitalWrite(HIGH)` closes the relay

**Ack timeout (device shows offline after activation)**

* The backend expects an ack within 10 seconds of sending `"on"`
* Check that `topicAck()` publishes successfully — look for `[MQTT] ack sent` in serial

***

## Next steps

* [Device contract](/devices/mqtt-contract) — full MQTT topic and handshake reference
* [Dashboard](https://app.pumplink.dev) — manage devices, users, and schedules
* Support: [hello@pumplink.dev](mailto:hello@pumplink.dev)
