·13 min read·BASHCAT 技術團隊·iot

ESP32 IoT Development in Practice: Complete MQTT + LoRa Application

A deep dive into ESP32 IoT development, from WiFi connectivity and MQTT communication to LoRa long-range transmission, building a complete IoT solution. Includes advanced techniques such as low-power design and OTA updates.

#ESP32#IoT#MQTT#LoRa#WiFi#Internet of Things#Embedded Development

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:

  1. WiFi Stability - Implement reconnection mechanisms
  2. Low-Power Design - Deep sleep and fast wake-up
  3. Communication Protocols - MQTT for cloud, LoRa for long range
  4. OTA Updates - Remote maintenance capability
  5. 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!

More from the workshop.