Skip to main content
CloudStudio

ESP32 Integration Example (MQTT)

This example connects an ESP32 board to Cloud Studio IoT over MQTT with TLS, using flexible data exchange. When you finish, the board will:

  • Send the temperature and humidity measured by a DHT22 sensor every minute (uplink).
  • Turn its LED on or off when you send a command from a dashboard or a rule (downlink), and report the new state back.

The same code works on any ESP32 family board supported by the Arduino core for ESP32 (ESP32, ESP32-S2, ESP32-S3, ESP32-C3, ESP32-C6). Only the pin numbers may change.

What You Need

  • An ESP32 development board and a DHT22 sensor connected to GPIO 4. Most DevKit boards have an LED on GPIO 2; if yours does not, connect one with a resistor.
  • The Arduino IDE with the esp32 board package by Espressif Systems installed from the Boards Manager.
  • Two libraries from the Library Manager: PubSubClient (by Nick O'Leary) and DHT sensor library (by Adafruit).
  • Access to the Manager application of your Cloud Studio IoT instance.

Step 1: Get the MQTT Credentials and Connection Data

Each instance has its own MQTT server. In the Manager, open MQTT Configuration. The page has two tabs:

  • MQTT users: copy the username and use the option to view its password. The username is also the prefix of every MQTT topic.
  • Connection data: copy the server and the port (usually 8883), and use Download certificate to get the mqtt_cert.pem file. The ESP32 uses it to check that it is talking to your server.

More details in MQTT integration.

Step 2: Create the Device Model

The model tells the platform which endpoints the device has and how to read its messages. In the Manager, go to Devices, then Device Models, select Add and enter a description (for example, "ESP32 DHT22") and a model code. Then edit the model and add the two scripts below. More details in device models.

Configuration script. It creates three endpoints in every device of this model: temperature, humidity and the LED.

function getConfiguration(config)
{
	config.addressLabel = {en: "MAC address", es: "Dirección MAC"};
}

function getEndpoints(deviceAddress, endpoints)
{
	endpoints.addEndpoint("1", "Temperature", endpointType.temperatureSensor);
	endpoints.addEndpoint("2", "Humidity", endpointType.humiditySensor);
	endpoints.addEndpoint("3", "LED", endpointType.appliance);
}

Data conversion script. parseUplink reads the JSON that the ESP32 publishes and updates the endpoints. buildDownlink turns an on/off command into a JSON message for the board.

function parseUplink(device, payload)
{
	var data = payload.asJsonObject();
	if (data === undefined || data === null)
		return;

	if (data.temperature !== undefined)
		device.endpoints.byAddress("1").updateTemperatureSensorStatus(data.temperature);

	if (data.humidity !== undefined)
		device.endpoints.byAddress("2").updateHumiditySensorStatus(data.humidity);

	if (data.led !== undefined)
		device.endpoints.byAddress("3").updateApplianceStatus(data.led);
}

function buildDownlink(device, endpoint, command, payload)
{
	payload.buildResult = downlinkBuildResult.unsupported;
	if (endpoint === null || command.type !== commandType.onOff)
		return;

	switch (command.onOff.type) {
		case onOffCommandType.turnOn:
			payload.setAsJsonObject({ on: true });
			break;
		case onOffCommandType.turnOff:
			payload.setAsJsonObject({ on: false });
			break;
		default:
			return;
	}
	payload.buildResult = downlinkBuildResult.ok;
}

The full reference for these functions is in data processing.

Step 3: Create the Device

Create a new device with the model from step 2. As the address, use the MAC address of the board without colons, for example 24A160F1B2C4. The platform creates the three endpoints automatically.

With the username and the address, the two topics of the device are:

  • Uplink (the ESP32 publishes): {MQTTUserID}/uplink/{DeviceAddress}
  • Downlink (the ESP32 subscribes): {MQTTUserID}/downlink/{DeviceAddress}

Step 4: Program the ESP32

Replace the values at the top of the sketch with your own data and paste the content of mqtt_cert.pem between the two certificate lines.

#include <WiFi.h>
#include <WiFiClientSecure.h>
#include <PubSubClient.h>
#include <DHT.h>

const char* WIFI_SSID = "your-wifi";
const char* WIFI_PASSWORD = "your-wifi-password";

const char* MQTT_HOST = "your-mqtt-server";   // Connection data tab
const uint16_t MQTT_PORT = 8883;
const char* MQTT_USER = "YOUR_MQTT_USER";      // MQTT users tab
const char* MQTT_PASSWORD = "YOUR_MQTT_PASSWORD";
const char* DEVICE_ADDRESS = "24A160F1B2C4";   // as entered in the platform

// Content of mqtt_cert.pem
const char* MQTT_CA_CERT = R"PEM(
-----BEGIN CERTIFICATE-----
PASTE THE CERTIFICATE HERE
-----END CERTIFICATE-----
)PEM";

const int DHT_PIN = 4;
const int LED_PIN = 2;
const unsigned long PUBLISH_INTERVAL_MS = 60000;

DHT dht(DHT_PIN, DHT22);
WiFiClientSecure tlsClient;
PubSubClient mqtt(tlsClient);

char uplinkTopic[96];
char downlinkTopic[96];
bool ledOn = false;
unsigned long lastPublish = 0;

void publishState() {
  float temperature = dht.readTemperature();
  float humidity = dht.readHumidity();
  char message[96];

  if (isnan(temperature) || isnan(humidity)) {
    snprintf(message, sizeof(message), "{\"led\":%s}", ledOn ? "true" : "false");
  } else {
    snprintf(message, sizeof(message), "{\"temperature\":%.1f,\"humidity\":%.1f,\"led\":%s}",
             temperature, humidity, ledOn ? "true" : "false");
  }

  mqtt.publish(uplinkTopic, message);
  Serial.printf("Sent %s\n", message);
}

void onMessage(char* topic, byte* payload, unsigned int length) {
  String message;
  for (unsigned int i = 0; i < length; i++) {
    message += (char)payload[i];
  }
  message.replace(" ", "");

  if (message.indexOf("\"on\":true") >= 0) {
    ledOn = true;
  } else if (message.indexOf("\"on\":false") >= 0) {
    ledOn = false;
  } else {
    return;
  }

  digitalWrite(LED_PIN, ledOn ? HIGH : LOW);
  publishState();
}

void connectWiFi() {
  WiFi.mode(WIFI_STA);
  WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  Serial.printf("\nWi-Fi connected, IP %s\n", WiFi.localIP().toString().c_str());
}

void connectMqtt() {
  String clientId = String(MQTT_USER) + DEVICE_ADDRESS;
  while (!mqtt.connected()) {
    if (mqtt.connect(clientId.c_str(), MQTT_USER, MQTT_PASSWORD)) {
      mqtt.subscribe(downlinkTopic);
      Serial.println("MQTT connected");
    } else {
      Serial.printf("MQTT connection failed, state %d. Retrying in 5 s\n", mqtt.state());
      delay(5000);
    }
  }
}

void setup() {
  Serial.begin(115200);
  pinMode(LED_PIN, OUTPUT);
  digitalWrite(LED_PIN, LOW);
  dht.begin();

  snprintf(uplinkTopic, sizeof(uplinkTopic), "%s/uplink/%s", MQTT_USER, DEVICE_ADDRESS);
  snprintf(downlinkTopic, sizeof(downlinkTopic), "%s/downlink/%s", MQTT_USER, DEVICE_ADDRESS);

  connectWiFi();
  tlsClient.setCACert(MQTT_CA_CERT);
  mqtt.setServer(MQTT_HOST, MQTT_PORT);
  mqtt.setCallback(onMessage);
}

void loop() {
  if (WiFi.status() != WL_CONNECTED) {
    connectWiFi();
  }
  if (!mqtt.connected()) {
    connectMqtt();
  }
  mqtt.loop();

  if (lastPublish == 0 || millis() - lastPublish >= PUBLISH_INTERVAL_MS) {
    lastPublish = millis();
    publishState();
  }
}

The ESP32 publishes messages like this one to the uplink topic:

{"temperature":22.4,"humidity":48.0,"led":false}

And it receives messages like this one on the downlink topic:

{"on":true}

Step 5: Check That It Works

  • Open the serial monitor at 115200 baud. You should see "MQTT connected" and one "Sent" line per minute.
  • The device should show the temperature and humidity values in the platform. To see the raw messages as they arrive, use the real-time log broker.
  • Turn the LED endpoint on from the platform. The LED on the board turns on and the endpoint shows the new state.

With data coming in, you can build dashboards and alarms on top of it.

If the connection fails: in PubSubClient, state -2 usually means that the TLS connection could not be established (check the server, the port and that the certificate is the one downloaded from your instance), and states 4 or 5 mean that the username or password were rejected. If the board connects but no data appears, check that the topic uses the same username and device address as the platform, character by character.

Last updated on

On this page