返回部落格

ESP32 物聯網開發實戰:MQTT + LoRa 完整應用

深入ESP32物聯網開發,從WiFi連接、MQTT通訊到LoRa遠距傳輸,打造完整的IoT解決方案。包含低功耗設計、OTA更新等進階技巧。

BASHCAT 技術團隊
13 分鐘閱讀
#ESP32#IoT#MQTT#LoRa#WiFi#物聯網#嵌入式開發

ESP32 物聯網開發實戰:MQTT + LoRa 完整應用

ESP32 是當前最熱門的物聯網開發板,整合 WiFi、藍芽、雙核心處理器,價格親民且功能強大。本文將分享ESP32物聯網開發的完整實戰經驗。

ESP32 核心優勢

硬體規格

CPU: Xtensa LX6 雙核心 @ 240MHz
RAM: 520KB SRAM
Flash: 4MB (可擴展)
WiFi: 802.11 b/g/n (2.4GHz)
藍芽: BLE 4.2 + Classic
GPIO: 34 個可程式化 I/O
ADC: 18 通道 12-bit
DAC: 2 通道 8-bit
觸控感測: 10 個觸控引腳

開發環境設置

# Arduino IDE 方式
# 1. 安裝 Arduino IDE
# 2. 新增 ESP32 開發板管理員網址
# https://raw.githubusercontent.com/espressif/arduino-esp32/gh-pages/package_esp32_index.json

# PlatformIO 方式(推薦)
pip install platformio

# 建立專案
pio project init --board esp32dev

# platformio.ini
[env:esp32dev]
platform = espressif32
board = esp32dev
framework = arduino
monitor_speed = 115200

# 編譯上傳
pio run --target upload
pio device monitor

MQTT 物聯網通訊

基礎 MQTT 實作

// mqtt_basic.ino
#include <WiFi.h>
#include <PubSubClient.h>

// WiFi 設定
const char* ssid = "YOUR_WIFI_SSID";
const char* password = "YOUR_WIFI_PASSWORD";

// MQTT 設定
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);

// 主題定義
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);
    
    // 連接 WiFi
    setupWiFi();
    
    // 設定 MQTT
    mqtt.setServer(mqtt_server, mqtt_port);
    mqtt.setCallback(mqttCallback);
    
    // 連接 MQTT
    connectMQTT();
}

void setupWiFi() {
    Serial.print("連接 WiFi...");
    WiFi.begin(ssid, password);
    
    while (WiFi.status() != WL_CONNECTED) {
        delay(500);
        Serial.print(".");
    }
    
    Serial.println("\n✓ WiFi 已連接");
    Serial.print("IP: ");
    Serial.println(WiFi.localIP());
}

void connectMQTT() {
    while (!mqtt.connected()) {
        Serial.print("連接 MQTT Broker...");
        
        String clientId = "ESP32-" + String(WiFi.macAddress());
        
        if (mqtt.connect(clientId.c_str(), mqtt_user, mqtt_password)) {
            Serial.println("✓ MQTT 已連接");
            
            // 訂閱主題
            mqtt.subscribe(TOPIC_COMMAND);
            
            // 發送上線通知
            mqtt.publish(TOPIC_STATUS, "online");
        } else {
            Serial.print("✗ 連接失敗, rc=");
            Serial.println(mqtt.state());
            delay(5000);
        }
    }
}

void mqttCallback(char* topic, byte* payload, unsigned int length) {
    Serial.print("收到訊息 [");
    Serial.print(topic);
    Serial.print("]: ");
    
    String message;
    for (int i = 0; i < length; i++) {
        message += (char)payload[i];
    }
    Serial.println(message);
    
    // 處理命令
    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() {
    // 確保 MQTT 連接
    if (!mqtt.connected()) {
        connectMQTT();
    }
    mqtt.loop();
    
    // 每 30 秒發送一次溫度數據
    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() {
    // 這裡使用 DHT22 或其他溫度感測器
    return 25.5;  // 示例值
}

LoRa 遠距通訊

LoRa 硬體接線

ESP32      LoRa SX1278
─────────────────────
3.3V   →   VCC
GND    →   GND
GPIO5  →   NSS (CS)
GPIO18 →   SCK
GPIO19 →   MISO
GPIO23 →   MOSI
GPIO2  →   RST
GPIO26 →   DIO0

LoRa 通訊實作

// lora_communication.ino
#include <SPI.h>
#include <LoRa.h>

#define SS 5
#define RST 2
#define DIO0 26

// LoRa 頻段設定
#define BAND 915E6  // 915MHz for AS923 (台灣)

String deviceId = "NODE_001";

void setup() {
    Serial.begin(115200);
    
    // 初始化 LoRa
    SPI.begin();
    LoRa.setPins(SS, RST, DIO0);
    
    if (!LoRa.begin(BAND)) {
        Serial.println("LoRa 初始化失敗!");
        while (1);
    }
    
    // LoRa 參數配置
    LoRa.setSpreadingFactor(7);     // 擴頻因子 7-12
    LoRa.setSignalBandwidth(125E3); // 帶寬 125KHz
    LoRa.setCodingRate4(5);         // 編碼率 4/5
    LoRa.setTxPower(20);            // 發射功率 20dBm
    
    Serial.println("✓ LoRa 已初始化");
}

void loop() {
    // 發送數據
    sendLoRaData();
    
    // 接收數據
    receiveLoRaData();
    
    delay(5000);
}

void sendLoRaData() {
    // 讀取感測器
    float temperature = 25.5;
    float humidity = 60.0;
    int battery = 85;
    
    // 建立 JSON 封包
    String packet = "{";
    packet += "\"id\":\"" + deviceId + "\",";
    packet += "\"temp\":" + String(temperature, 2) + ",";
    packet += "\"hum\":" + String(humidity, 2) + ",";
    packet += "\"bat\":" + String(battery);
    packet += "}";
    
    // 發送封包
    LoRa.beginPacket();
    LoRa.print(packet);
    LoRa.endPacket();
    
    Serial.println("📤 發送: " + packet);
}

void receiveLoRaData() {
    int packetSize = LoRa.parsePacket();
    
    if (packetSize) {
        String received = "";
        
        // 讀取封包
        while (LoRa.available()) {
            received += (char)LoRa.read();
        }
        
        // 取得 RSSI
        int rssi = LoRa.packetRssi();
        float snr = LoRa.packetSnr();
        
        Serial.println("📥 收到: " + received);
        Serial.printf("   RSSI: %d dBm, SNR: %.2f dB\n", rssi, snr);
        
        // 解析並處理數據
        parseLoRaData(received);
    }
}

void parseLoRaData(String data) {
    // 使用 ArduinoJson 解析
    // 或簡單的字串處理
}

低功耗設計

// deep_sleep_example.ino
#include "esp_sleep.h"

#define uS_TO_S_FACTOR 1000000
#define TIME_TO_SLEEP  60  // 60 秒

RTC_DATA_ATTR int bootCount = 0;

void setup() {
    Serial.begin(115200);
    delay(1000);
    
    // 增加啟動計數
    ++bootCount;
    Serial.println("啟動次數: " + String(bootCount));
    
    // 讀取感測器並發送數據
    float data = readSensorAndSend();
    
    // 進入深度睡眠
    goToDeepSleep();
}

void loop() {
    // 深度睡眠模式不會執行 loop
}

void goToDeepSleep() {
    Serial.println("進入深度睡眠 " + String(TIME_TO_SLEEP) + " 秒");
    Serial.flush();
    
    // 設定喚醒時間
    esp_sleep_enable_timer_wakeup(TIME_TO_SLEEP * uS_TO_S_FACTOR);
    
    // 或使用外部喚醒
    // esp_sleep_enable_ext0_wakeup(GPIO_NUM_33, 1);
    
    // 進入深度睡眠
    esp_deep_sleep_start();
}

float readSensorAndSend() {
    // 讀取感測器
    float value = 25.5;
    
    // 快速連接 WiFi 並發送
    quickConnectAndSend(value);
    
    return value;
}

void quickConnectAndSend(float data) {
    // 使用靜態 IP 加速連接
    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);
    
    // 等待連接(最多 10 秒)
    int timeout = 0;
    while (WiFi.status() != WL_CONNECTED && timeout < 20) {
        delay(500);
        timeout++;
    }
    
    if (WiFi.status() == WL_CONNECTED) {
        // 發送數據到伺服器
        sendDataToServer(data);
    }
    
    WiFi.disconnect(true);
}

OTA 遠端更新

// 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);
    }
    
    // 配置 OTA
    ArduinoOTA.setHostname("ESP32-OTA");
    ArduinoOTA.setPassword("admin123");
    
    ArduinoOTA.onStart([]() {
        String type = (ArduinoOTA.getCommand() == U_FLASH) ? "sketch" : "filesystem";
        Serial.println("開始 OTA 更新: " + type);
    });
    
    ArduinoOTA.onEnd([]() {
        Serial.println("\n✓ OTA 更新完成");
    });
    
    ArduinoOTA.onProgress([](unsigned int progress, unsigned int total) {
        Serial.printf("進度: %u%%\r", (progress / (total / 100)));
    });
    
    ArduinoOTA.onError([](ota_error_t error) {
        Serial.printf("✗ 錯誤[%u]: ", error);
        if (error == OTA_AUTH_ERROR) Serial.println("認證失敗");
        else if (error == OTA_BEGIN_ERROR) Serial.println("開始失敗");
        else if (error == OTA_CONNECT_ERROR) Serial.println("連接失敗");
        else if (error == OTA_RECEIVE_ERROR) Serial.println("接收失敗");
        else if (error == OTA_END_ERROR) Serial.println("結束失敗");
    });
    
    ArduinoOTA.begin();
    Serial.println("✓ OTA 已啟動");
}

void loop() {
    ArduinoOTA.handle();
    
    // 你的程式邏輯...
}

完整專案:環境監測站

// 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);
    
    // 初始化感測器
    dht.begin();
    if (!bmp.begin(0x76)) {
        Serial.println("BMP280 初始化失敗");
    }
    
    // WiFi 和 MQTT
    setupWiFi();
    mqtt.setServer("mqtt.example.com", 1883);
    connectMQTT();
}

void loop() {
    if (!mqtt.connected()) {
        connectMQTT();
    }
    mqtt.loop();
    
    // 每分鐘讀取一次
    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("📤 " + String(payload));
}

總結

ESP32 是物聯網開發的理想選擇,掌握要點:

  1. WiFi 穩定性 - 實作重連機制
  2. 低功耗設計 - 深度睡眠與快速喚醒
  3. 通訊協定 - MQTT 雲端、LoRa 遠距
  4. OTA 更新 - 遠端維護能力
  5. 錯誤處理 - 看門狗與異常恢復

在 BASHCAT,我們精通 ESP32 物聯網開發,可協助您快速打造穩定可靠的IoT解決方案。歡迎與我們聯繫!

延伸閱讀

探索更多相關的技術洞察與開發經驗分享

更多 iot 文章

即將推出更多相關技術分享

查看全部文章