ESP32 IoT Development in Practice: Complete MQTT + LoRa Application
ESP32 is currently the most popular IoT development board, integrating WiFi, Bluetooth, and a dual-core processor at an affordable price with powerful capabilities. This article shares comprehensive hands-on experience with ESP32 IoT development.
ESP32 Core Advantages
Hardware Specifications
CPU: Xtensa LX6 Dual-Core @ 240MHz
RAM: 520KB SRAM
Flash: 4MB (expandable)
WiFi: 802.11 b/g/n (2.4GHz)
Bluetooth: BLE 4.2 + Classic
GPIO: 34 programmable I/O pins
ADC: 18-channel 12-bit
DAC: 2-channel 8-bit
Touch Sensing: 10 touch pins
Development Environment Setup
# Arduino IDE method
# 1. Install Arduino IDE
# 2. Add ESP32 Board Manager URL
# https://raw.githubusercontent.com/espressif/arduino-esp32/gh-pages/package_esp32_index.json
# PlatformIO method (recommended)
pip install platformio
# Create project
pio project init --board esp32dev
# platformio.ini
[env:esp32dev]
platform = espressif32
board = esp32dev
framework = arduino
monitor_speed = 115200
# Compile and upload
pio run --target upload
pio device monitor
MQTT IoT Communication
Basic MQTT Implementation
// mqtt_basic.ino
#include <WiFi.h>
#include <PubSubClient.h>
// WiFi configuration
const char* ssid = "YOUR_WIFI_SSID";
const char* password = "YOUR_WIFI_PASSWORD";
// MQTT configuration
const char* mqtt_server = "mqtt.example.com";
const int mqtt_port = 1883;
const char* mqtt_user = "your_username";
const char* mqtt_password = "your_password";
WiFiClient espClient;
PubSubClient mqtt(espClient);
// Topic definitions
const char* TOPIC_STATUS = "sensor/esp32/status";
const char* TOPIC_TEMPERATURE = "sensor/esp32/temperature";
const char* TOPIC_COMMAND = "command/esp32";
void setup() {
Serial.begin(115200);
// Connect to WiFi
setupWiFi();
// Configure MQTT
mqtt.setServer(mqtt_server, mqtt_port);
mqtt.setCallback(mqttCallback);
// Connect to MQTT
connectMQTT();
}
void setupWiFi() {
Serial.print("Connecting to WiFi...");
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println("\nWiFi connected");
Serial.print("IP: ");
Serial.println(WiFi.localIP());
}
void connectMQTT() {
while (!mqtt.connected()) {
Serial.print("Connecting to MQTT Broker...");
String clientId = "ESP32-" + String(WiFi.macAddress());
if (mqtt.connect(clientId.c_str(), mqtt_user, mqtt_password)) {
Serial.println("MQTT connected");
// Subscribe to topic
mqtt.subscribe(TOPIC_COMMAND);
// Send online notification
mqtt.publish(TOPIC_STATUS, "online");
} else {
Serial.print("Connection failed, rc=");
Serial.println(mqtt.state());
delay(5000);
}
}
}
void mqttCallback(char* topic, byte* payload, unsigned int length) {
Serial.print("Message received [");
Serial.print(topic);
Serial.print("]: ");
String message;
for (int i = 0; i < length; i++) {
message += (char)payload[i];
}
Serial.println(message);
// Handle commands
if (String(topic) == TOPIC_COMMAND) {
handleCommand(message);
}
}
void handleCommand(String command) {
if (command == "LED_ON") {
digitalWrite(LED_BUILTIN, HIGH);
mqtt.publish(TOPIC_STATUS, "LED: ON");
}
else if (command == "LED_OFF") {
digitalWrite(LED_BUILTIN, LOW);
mqtt.publish(TOPIC_STATUS, "LED: OFF");
}
else if (command == "RESTART") {
ESP.restart();
}
}
void loop() {
// Ensure MQTT connection
if (!mqtt.connected()) {
connectMQTT();
}
mqtt.loop();
// Send temperature data every 30 seconds
static unsigned long lastSend = 0;
if (millis() - lastSend > 30000) {
float temperature = readTemperature();
char tempString[8];
dtostrf(temperature, 6, 2, tempString);
mqtt.publish(TOPIC_TEMPERATURE, tempString);
lastSend = millis();
}
}
float readTemperature() {
// Use DHT22 or another temperature sensor here
return 25.5; // Example value
}
LoRa Long-Range Communication
LoRa Hardware Wiring
ESP32 LoRa SX1278
─────────────────────
3.3V → VCC
GND → GND
GPIO5 → NSS (CS)
GPIO18 → SCK
GPIO19 → MISO
GPIO23 → MOSI
GPIO2 → RST
GPIO26 → DIO0
LoRa Communication Implementation
// lora_communication.ino
#include <SPI.h>
#include <LoRa.h>
#define SS 5
#define RST 2
#define DIO0 26
// LoRa frequency band configuration
#define BAND 915E6 // 915MHz for AS923 (Taiwan)
String deviceId = "NODE_001";
void setup() {
Serial.begin(115200);
// Initialize LoRa
SPI.begin();
LoRa.setPins(SS, RST, DIO0);
if (!LoRa.begin(BAND)) {
Serial.println("LoRa initialization failed!");
while (1);
}
// LoRa parameter configuration
LoRa.setSpreadingFactor(7); // Spreading factor 7-12
LoRa.setSignalBandwidth(125E3); // Bandwidth 125KHz
LoRa.setCodingRate4(5); // Coding rate 4/5
LoRa.setTxPower(20); // Transmit power 20dBm
Serial.println("LoRa initialized");
}
void loop() {
// Send data
sendLoRaData();
// Receive data
receiveLoRaData();
delay(5000);
}
void sendLoRaData() {
// Read sensors
float temperature = 25.5;
float humidity = 60.0;
int battery = 85;
// Build JSON packet
String packet = "{";
packet += "\"id\":\"" + deviceId + "\",";
packet += "\"temp\":" + String(temperature, 2) + ",";
packet += "\"hum\":" + String(humidity, 2) + ",";
packet += "\"bat\":" + String(battery);
packet += "}";
// Send packet
LoRa.beginPacket();
LoRa.print(packet);
LoRa.endPacket();
Serial.println("Sent: " + packet);
}
void receiveLoRaData() {
int packetSize = LoRa.parsePacket();
if (packetSize) {
String received = "";
// Read packet
while (LoRa.available()) {
received += (char)LoRa.read();
}
// Get RSSI
int rssi = LoRa.packetRssi();
float snr = LoRa.packetSnr();
Serial.println("Received: " + received);
Serial.printf(" RSSI: %d dBm, SNR: %.2f dB\n", rssi, snr);
// Parse and process data
parseLoRaData(received);
}
}
void parseLoRaData(String data) {
// Parse using ArduinoJson
// or simple string processing
}
Low-Power Design
// deep_sleep_example.ino
#include "esp_sleep.h"
#define uS_TO_S_FACTOR 1000000
#define TIME_TO_SLEEP 60 // 60 seconds
RTC_DATA_ATTR int bootCount = 0;
void setup() {
Serial.begin(115200);
delay(1000);
// Increment boot count
++bootCount;
Serial.println("Boot count: " + String(bootCount));
// Read sensor and send data
float data = readSensorAndSend();
// Enter deep sleep
goToDeepSleep();
}
void loop() {
// loop() will not execute in deep sleep mode
}
void goToDeepSleep() {
Serial.println("Entering deep sleep for " + String(TIME_TO_SLEEP) + " seconds");
Serial.flush();
// Set wake-up timer
esp_sleep_enable_timer_wakeup(TIME_TO_SLEEP * uS_TO_S_FACTOR);
// Or use external wake-up
// esp_sleep_enable_ext0_wakeup(GPIO_NUM_33, 1);
// Enter deep sleep
esp_deep_sleep_start();
}
float readSensorAndSend() {
// Read sensor
float value = 25.5;
// Quickly connect to WiFi and send
quickConnectAndSend(value);
return value;
}
void quickConnectAndSend(float data) {
// Use static IP for faster connection
IPAddress local_IP(192, 168, 1, 100);
IPAddress gateway(192, 168, 1, 1);
IPAddress subnet(255, 255, 255, 0);
WiFi.config(local_IP, gateway, subnet);
WiFi.begin(ssid, password);
// Wait for connection (max 10 seconds)
int timeout = 0;
while (WiFi.status() != WL_CONNECTED && timeout < 20) {
delay(500);
timeout++;
}
if (WiFi.status() == WL_CONNECTED) {
// Send data to server
sendDataToServer(data);
}
WiFi.disconnect(true);
}
OTA Remote Updates
// ota_update.ino
#include <WiFi.h>
#include <ESPmDNS.h>
#include <WiFiUdp.h>
#include <ArduinoOTA.h>
void setup() {
Serial.begin(115200);
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
}
// Configure OTA
ArduinoOTA.setHostname("ESP32-OTA");
ArduinoOTA.setPassword("admin123");
ArduinoOTA.onStart([]() {
String type = (ArduinoOTA.getCommand() == U_FLASH) ? "sketch" : "filesystem";
Serial.println("Starting OTA update: " + type);
});
ArduinoOTA.onEnd([]() {
Serial.println("\nOTA update complete");
});
ArduinoOTA.onProgress([](unsigned int progress, unsigned int total) {
Serial.printf("Progress: %u%%\r", (progress / (total / 100)));
});
ArduinoOTA.onError([](ota_error_t error) {
Serial.printf("Error[%u]: ", error);
if (error == OTA_AUTH_ERROR) Serial.println("Authentication failed");
else if (error == OTA_BEGIN_ERROR) Serial.println("Begin failed");
else if (error == OTA_CONNECT_ERROR) Serial.println("Connection failed");
else if (error == OTA_RECEIVE_ERROR) Serial.println("Receive failed");
else if (error == OTA_END_ERROR) Serial.println("End failed");
});
ArduinoOTA.begin();
Serial.println("OTA ready");
}
void loop() {
ArduinoOTA.handle();
// Your application logic...
}
Complete Project: Environmental Monitoring Station
// environment_monitor.ino
#include <WiFi.h>
#include <PubSubClient.h>
#include <DHT.h>
#include <Wire.h>
#include <Adafruit_BMP280.h>
#define DHTPIN 4
#define DHTTYPE DHT22
DHT dht(DHTPIN, DHTTYPE);
Adafruit_BMP280 bmp;
WiFiClient espClient;
PubSubClient mqtt(espClient);
struct SensorData {
float temperature;
float humidity;
float pressure;
float altitude;
int airQuality;
unsigned long timestamp;
};
void setup() {
Serial.begin(115200);
// Initialize sensors
dht.begin();
if (!bmp.begin(0x76)) {
Serial.println("BMP280 initialization failed");
}
// WiFi and MQTT
setupWiFi();
mqtt.setServer("mqtt.example.com", 1883);
connectMQTT();
}
void loop() {
if (!mqtt.connected()) {
connectMQTT();
}
mqtt.loop();
// Read once per minute
static unsigned long lastRead = 0;
if (millis() - lastRead > 60000) {
SensorData data = readAllSensors();
publishSensorData(data);
lastRead = millis();
}
}
SensorData readAllSensors() {
SensorData data;
data.temperature = dht.readTemperature();
data.humidity = dht.readHumidity();
data.pressure = bmp.readPressure() / 100.0;
data.altitude = bmp.readAltitude(1013.25);
data.airQuality = analogRead(35); // MQ-135
data.timestamp = millis();
return data;
}
void publishSensorData(SensorData data) {
char payload[256];
sprintf(payload,
"{\"temp\":%.2f,\"hum\":%.2f,\"pres\":%.2f,\"alt\":%.2f,\"aqi\":%d}",
data.temperature, data.humidity, data.pressure,
data.altitude, data.airQuality
);
mqtt.publish("sensor/environment", payload);
Serial.println("Sent: " + String(payload));
}
Conclusion
ESP32 is an ideal choice for IoT development. Key takeaways:
- WiFi Stability - Implement reconnection mechanisms
- Low-Power Design - Deep sleep and fast wake-up
- Communication Protocols - MQTT for cloud, LoRa for long range
- OTA Updates - Remote maintenance capability
- Error Handling - Watchdog timer and exception recovery
At BASHCAT, we specialize in ESP32 IoT development and can help you rapidly build stable and reliable IoT solutions. Feel free to contact us!