Created
January 11, 2026 04:02
-
-
Save quan-vu/d5479efc9f6ef300ffe4ea25e6e9aff6 to your computer and use it in GitHub Desktop.
Hướng dẫn tích hợp ESP32 với IoTLabs Cloud MQTT trên Arduino IDE (nâng cao)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| /* | |
| IoTLabs ESP32 WiFi + MQTT (TLS) Core | |
| -------------------------------------------------- | |
| Yêu cầu: | |
| - Board: ESP32 (Arduino IDE) | |
| - Libraries: WiFi, WiFiClientSecure, PubSubClient | |
| - Device Info: Đã tạo device trên IoTLabs Dashboard và lấy thông tin kết nối MQTT thành công | |
| Ghi chú: | |
| - Code đã được đóng gói sẵn thành các hàm WiFi/MQTT “lõi”, rõ ràng và độc lập | |
| bạn chỉ cần copy/paste vào dự án là chạy | |
| - Bạn không cần chỉnh sửa logic WiFi/MQTT: chỉ việc cập nhật phần CONFIG (SSID/PASS, MQTT host/user/pass, topic) | |
| rồi gọi các hàm public là xong. | |
| - Hoặc copy code đã tạo tự động trên trang chi tiết thiết bị (https://dashboard.iotlabs.vn/devices/DEVICE_ID/integration | |
| Lưu ý: | |
| - Thiết bị sử dụng kết nối bảo mật TLS cần đúng Cert CA để server verify. | |
| - BẮT BUỘC sync NTP trước khi verify cert. | |
| - setCACert() cần Root CA (ISRG Root X1), KHÔNG phải fullchain.pem. | |
| */ | |
| #include <WiFi.h> | |
| #include <WiFiClientSecure.h> | |
| #include <PubSubClient.h> | |
| #include <time.h> | |
| // ===================================================== | |
| // 1) CONFIG (Cấu hình cho MQTT) | |
| // ===================================================== | |
| namespace Config { | |
| // WiFi | |
| static const char* WIFI_SSID = "your_wifi"; | |
| static const char* WIFI_PASS = "your_pass"; | |
| // MQTT TLS | |
| static const char* MQTT_HOST = "mqtt.iotlabs.vn"; // dùng HOSTNAME để SNI hoạt động | |
| static const uint16_t MQTT_PORT = 8883; | |
| // Auth (RabbitMQ MQTT plugin) | |
| static const char* MQTT_USER = "/iotlabs:d_696309d71a367b6c5fb9663e"; // Thay bằng mqtt devide username của bạn | |
| static const char* MQTT_PASS = "PASSWORD"; // thay bằng mqtt device password | |
| // Identity | |
| static const char* DEVICE_ID = "d_696309d71a367b6c5fb9663e"; // dùng dạng d_xxx đồng bộ topic | |
| static const char* ORG_ID = "69622509adad93591b82ebcb"; | |
| // Topics | |
| // iotlabs/<orgId>/devices/<deviceId>/ping | |
| // iotlabs/<orgId>/devices/<deviceId>/telemetry | |
| static const char* TOPIC_PING = "iotlabs/69622509adad93591b82ebcb/devices/d_696309d71a367b6c5fb9663e/ping"; | |
| static const char* TOPIC_TELEMETRY = "iotlabs/69622509adad93591b82ebcb/devices/d_696309d71a367b6c5fb9663e/telemetry"; | |
| // Schedule | |
| static const unsigned long TELEMETRY_INTERVAL_MS = 15000; // gửi telemetry mỗi 15s | |
| // Nếu muốn chạy “giờ cố định” theo local time (GMT+7) | |
| static const uint8_t DAILY_PING_HOUR = 8; | |
| static const uint8_t DAILY_PING_MIN = 0; | |
| } | |
| // ===================================================== | |
| // 2) TLS Root CA ( - ISRG Root X1) | |
| // ===================================================== | |
| // NOTE: Giữ nguyên chứng chỉ. | |
| static const char* LE_ISRG_ROOT_X1 PROGMEM = R"EOF( | |
| -----BEGIN CERTIFICATE----- | |
| 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 | |
| -----END CERTIFICATE----- | |
| )EOF"; | |
| // ===================================================== | |
| // 3) Core Context (giữ state tối thiểu) | |
| // ===================================================== | |
| struct CoreCtx { | |
| WiFiClientSecure net; | |
| PubSubClient mqtt; | |
| // interval schedule | |
| unsigned long lastTelemetryMs = 0; | |
| // fixed-time schedule (run once/day) | |
| int lastDailyPingYDay = -1; | |
| CoreCtx() : mqtt(net) {} | |
| }; | |
| static CoreCtx core; | |
| // ===================================================== | |
| // 4) Helpers (pure-ish functions) | |
| // ===================================================== | |
| static String mqttBuildClientId() { | |
| // ClientID nên ngắn + unique | |
| const uint32_t mac = (uint32_t)ESP.getEfuseMac(); | |
| return String("iotlabs_") + Config::DEVICE_ID + "_" + String(mac, HEX); | |
| } | |
| static bool timeIsValid() { | |
| // Nếu ESP32 chưa sync time, TLS verify cert sẽ fail | |
| const time_t now = time(nullptr); | |
| return now >= 1700000000; // ~ 2023-11-14 | |
| } | |
| static void logMqttFailure() { | |
| Serial.printf("MQTT connect failed, state=%d\n", core.mqtt.state()); | |
| char buf[256]; | |
| core.net.lastError(buf, sizeof(buf)); | |
| Serial.printf("TLS lastError: %s\n", buf); | |
| } | |
| // ===================================================== | |
| // 5) Public API: WiFi | |
| // ===================================================== | |
| void wifiSetup() { | |
| WiFi.mode(WIFI_STA); | |
| WiFi.setAutoReconnect(true); | |
| WiFi.persistent(true); | |
| } | |
| bool wifiConnect(uint32_t timeoutMs = 20000) { | |
| if (WiFi.status() == WL_CONNECTED) return true; | |
| Serial.printf("WiFi connecting to '%s'", Config::WIFI_SSID); | |
| WiFi.begin(Config::WIFI_SSID, Config::WIFI_PASS); | |
| const uint32_t start = millis(); | |
| while (WiFi.status() != WL_CONNECTED && (millis() - start) < timeoutMs) { | |
| Serial.print("."); | |
| delay(350); | |
| } | |
| Serial.println(); | |
| if (WiFi.status() == WL_CONNECTED) { | |
| Serial.printf("WiFi connected. IP: %s\n", WiFi.localIP().toString().c_str()); | |
| return true; | |
| } | |
| Serial.println("WiFi connect FAILED"); | |
| return false; | |
| } | |
| // ===================================================== | |
| // 6) Public API: Time Sync | |
| // ===================================================== | |
| void timeSyncGMT7(uint32_t maxWaitMs = 15000) { | |
| // VN GMT+7 | |
| configTime(7 * 3600, 0, "pool.ntp.org", "time.nist.gov"); | |
| Serial.print("Sync NTP"); | |
| const uint32_t start = millis(); | |
| while (!timeIsValid() && (millis() - start) < maxWaitMs) { | |
| Serial.print("."); | |
| delay(500); | |
| } | |
| Serial.println(); | |
| if (!timeIsValid()) { | |
| Serial.println("NTP sync FAILED -> TLS may fail"); | |
| } | |
| } | |
| // ===================================================== | |
| // 7) Public API: MQTT (TLS) | |
| // ===================================================== | |
| void mqttSetup() { | |
| // TLS trust store | |
| core.net.setCACert(LE_ISRG_ROOT_X1); | |
| core.net.setHandshakeTimeout(30); | |
| // MQTT client | |
| core.mqtt.setServer(Config::MQTT_HOST, Config::MQTT_PORT); | |
| core.mqtt.setKeepAlive(30); | |
| core.mqtt.setSocketTimeout(10); | |
| // Nếu bạn muốn subscribe/callback: core.mqtt.setCallback(...) | |
| } | |
| bool mqttConnect(int maxAttempts = 20, uint32_t retryDelayMs = 800) { | |
| if (core.mqtt.connected()) return true; | |
| Serial.print("MQTT connecting"); | |
| int attempts = 0; | |
| while (!core.mqtt.connected() && attempts < maxAttempts) { | |
| const String clientId = mqttBuildClientId(); | |
| const bool ok = core.mqtt.connect( | |
| clientId.c_str(), | |
| Config::MQTT_USER, | |
| Config::MQTT_PASS | |
| ); | |
| Serial.print(ok ? "✔" : "."); | |
| if (!ok) { | |
| Serial.println(); | |
| logMqttFailure(); | |
| delay(retryDelayMs); | |
| } | |
| attempts++; | |
| } | |
| Serial.println(); | |
| if (!core.mqtt.connected()) { | |
| Serial.println("MQTT connect FAILED after retries."); | |
| return false; | |
| } | |
| Serial.println("MQTT connected."); | |
| return true; | |
| } | |
| void mqttLoop() { | |
| // keep-alive & inbound packets | |
| if (core.mqtt.connected()) core.mqtt.loop(); | |
| } | |
| // ===================================================== | |
| // 8) Public API: Publish | |
| // ===================================================== | |
| bool mqttSendPing() { | |
| if (!core.mqtt.connected()) return false; | |
| // Payload: giữ nguyên structure bạn đang dùng | |
| String msg = "{"; | |
| msg += String("\"deviceId\":\"") + Config::DEVICE_ID + "\","; | |
| msg += String("\"ts\":") + String((unsigned long long)millis()) + ","; | |
| msg += "\"message\":\"ping\""; | |
| msg += "}"; | |
| const bool ok = core.mqtt.publish(Config::TOPIC_PING, msg.c_str()); | |
| if (ok) Serial.println("[MQTT] ping published"); | |
| else Serial.println("[MQTT] ping publish FAILED"); | |
| return ok; | |
| } | |
| bool mqttSendTelemetry(float temperature, float humidity, float batteryV) { | |
| if (!core.mqtt.connected()) return false; | |
| String payload = "{"; | |
| payload += String("\"deviceId\":\"") + Config::DEVICE_ID + "\","; | |
| payload += String("\"ts\":") + String((unsigned long long)millis()) + ","; | |
| payload += "\"metrics\":{"; | |
| payload += String("\"temperature\":") + String(temperature, 1) + ","; | |
| payload += String("\"humidity\":") + String(humidity, 0) + ","; | |
| payload += String("\"battery\":") + String(batteryV, 2) + ","; | |
| payload += String("\"rssi\":") + String(WiFi.RSSI()); | |
| payload += "}}"; | |
| const bool ok = core.mqtt.publish(Config::TOPIC_TELEMETRY, payload.c_str()); | |
| if (ok) Serial.println("[MQTT] telemetry published"); | |
| else Serial.println("[MQTT] telemetry publish FAILED"); | |
| return ok; | |
| } | |
| // ===================================================== | |
| // 9) Scheduling | |
| // ===================================================== | |
| // Phần này, cho phép chạy theo lịch hoặc thời gian cố định | |
| // gửi dữ liệu qua kết nối MQTT | |
| // 9.1 Interval schedule: chạy mỗi intervalMs | |
| struct IntervalSchedule { | |
| unsigned long intervalMs; | |
| }; | |
| static bool scheduleShouldRunInterval(unsigned long& lastRunMs, unsigned long intervalMs) { | |
| const unsigned long now = millis(); | |
| if (now - lastRunMs >= intervalMs) { | |
| lastRunMs = now; | |
| return true; | |
| } | |
| return false; | |
| } | |
| // 9.2 Fixed-time schedule: chạy 1 lần/ngày tại HH:MM (local time) | |
| static bool scheduleShouldRunDailyAt(uint8_t hour, uint8_t minute) { | |
| if (!timeIsValid()) return false; | |
| time_t now = time(nullptr); | |
| struct tm lt; | |
| localtime_r(&now, <); | |
| // Trigger đúng phút | |
| if (lt.tm_hour == hour && lt.tm_min == minute) { | |
| // Đảm bảo chỉ chạy 1 lần/ngày | |
| if (core.lastDailyPingYDay != lt.tm_yday) { | |
| core.lastDailyPingYDay = lt.tm_yday; | |
| return true; | |
| } | |
| } | |
| return false; | |
| } | |
| // API: chạy các schedule mẫu | |
| void mqttRunSchedule() { | |
| // (A) Interval telemetry | |
| if (scheduleShouldRunInterval(core.lastTelemetryMs, Config::TELEMETRY_INTERVAL_MS)) { | |
| // Demo sensor values (thay bằng cảm biến thật) | |
| const unsigned long now = millis(); | |
| float temperature = 25.0f + (now % 1000) / 100.0f; | |
| float humidity = 50.0f + (now % 500) / 50.0f; | |
| float batteryV = 3.90f; | |
| mqttSendTelemetry(temperature, humidity, batteryV); | |
| } | |
| // (B) Daily fixed-time ping (ví dụ 08:00) | |
| if (scheduleShouldRunDailyAt(Config::DAILY_PING_HOUR, Config::DAILY_PING_MIN)) { | |
| mqttSendPing(); | |
| } | |
| } | |
| // ===================================================== | |
| // 10) Bootstrap (recommended) | |
| // - Đã tối ưu logic Wifi -> MQTT -> Loop | |
| // ===================================================== | |
| // Hàm "coreSetup" cho mqtt dùng trong setup() | |
| void coreSetup() { | |
| Serial.begin(115200); | |
| delay(300); | |
| wifiSetup(); | |
| mqttSetup(); | |
| // 1) WiFi up | |
| if (wifiConnect()) { | |
| // 2) time sync (TLS) | |
| timeSyncGMT7(); | |
| } | |
| // 3) MQTT up | |
| if (mqttConnect()) { | |
| // Optional: ping once on connect | |
| mqttSendPing(); | |
| } | |
| } | |
| // Hàm "coreLoop": giữ kết nối + chạy schedule | |
| void coreLoop() { | |
| // WiFi reconnect | |
| if (WiFi.status() != WL_CONNECTED) { | |
| if (wifiConnect()) { | |
| timeSyncGMT7(); | |
| } | |
| } | |
| // MQTT reconnect | |
| if (!core.mqtt.connected()) { | |
| mqttConnect(); | |
| } | |
| mqttLoop(); | |
| mqttRunSchedule(); | |
| // Small delay to reduce busy loop | |
| delay(50); | |
| } | |
| // ===================================================== | |
| // 11) Final, Arduino entrypoints | |
| // ===================================================== | |
| void setup() { | |
| coreSetup(); | |
| } | |
| void loop() { | |
| coreLoop(); | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment