Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Select an option

  • Save quan-vu/d5479efc9f6ef300ffe4ea25e6e9aff6 to your computer and use it in GitHub Desktop.

Select an option

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)
/*
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-----
MIIFazCCA1OgAwIBAgIRAIIQz7DSQONZRGPgu2OCiwAwDQYJKoZIhvcNAQELBQAw
TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh
cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMTUwNjA0MTEwNDM4
WhcNMzUwNjA0MTEwNDM4WjBPMQswCQYDVQQGEwJVUzEpMCcGA1UEChMgSW50ZXJu
ZXQgU2VjdXJpdHkgUmVzZWFyY2ggR3JvdXAxFTATBgNVBAMTDElTUkcgUm9vdCBY
MTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK3oJHP0FDfzm54rVygc
h77ct984kIxuPOZXoHj3dcKi/vVqbvYATyjb3miGbESTtrFj/RQSa78f0uoxmyF+
0TM8ukj13Xnfs7j/EvEhmkvBioZxaUpmZmyPfjxwv60pIgbz5MDmgK7iS4+3mX6U
A5/TR5d8mUgjU+g4rk8Kb4Mu0UlXjIB0ttov0DiNewNwIRt18jA8+o+u3dpjq+sW
T8KOEUt+zwvo/7V3LvSye0rgTBIlDHCNAymg4VMk7BPZ7hm/ELNKjD+Jo2FR3qyH
B5T0Y3HsLuJvW5iB4YlcNHlsdu87kGJ55tukmi8mxdAQ4Q7e2RCOFvu396j3x+UC
B5iPNgiV5+I3lg02dZ77DnKxHZu8A/lJBdiB3QW0KtZB6awBdpUKD9jf1b0SHzUv
KBds0pjBqAlkd25HN7rOrFleaJ1/ctaJxQZBKT5ZPt0m9STJEadao0xAH0ahmbWn
OlFuhjuefXKnEgV4We0+UXgVCwOPjdAvBbI+e0ocS3MFEvzG6uBQE3xDk3SzynTn
jh8BCNAw1FtxNrQHusEwMFxIt4I7mKZ9YIqioymCzLq9gwQbooMDQaHWBfEbwrbw
qHyGO0aoSCqI3Haadr8faqU9GY/rOPNk3sgrDQoo//fb4hVC1CLQJ13hef4Y53CI
rU7m2Ys6xt0nUW7/vGT1M0NPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNV
HRMBAf8EBTADAQH/MB0GA1UdDgQWBBR5tFnme7bl5AFzgAiIyBpY9umbbjANBgkq
hkiG9w0BAQsFAAOCAgEAVR9YqbyyqFDQDLHYGmkgJykIrGF1XIpu+ILlaS/V9lZL
ubhzEFnTIZd+50xx+7LSYK05qAvqFyFWhfFQDlnrzuBZ6brJFe+GnY+EgPbk6ZGQ
3BebYhtF8GaV0nxvwuo77x/Py9auJ/GpsMiu/X1+mvoiBOv/2X/qkSsisRcOj/KK
NFtY2PwByVS5uCbMiogziUwthDyC3+6WVwW6LLv3xLfHTjuCvjHIInNzktHCgKQ5
ORAzI4JMPJ+GslWYHb4phowim57iaztXOoJwTdwJx4nLCgdNbOhdjsnvzqvHu7Ur
TkXWStAmzOVyyghqpZXjFaH3pO3JLF+l+/+sKAIuvtd7u+Nxe5AW0wdeRlN8NwdC
jNPElpzVmbUq4JUagEiuTDkHzsxHpFKVK7q4+63SM1N95R1NbdWhscdCb+ZAJzVc
oyi3B43njTOQ5yOf+1CceWxG1bQVs5ZufpsMljq4Ui0/1lvh+wjChP4kqKOJ2qxq
4RgqsahDYVvTH9w7jXbyLeiNdd8XM2w9U/t7y0Ff/9yi0GE44Za4rF2LN9d11TPA
mRGunUHBcnWEvgJBQl9nJEiU0Zsnvgc/ubhPgXRR4Xq37Z0j4r7g1SgEEzwxA57d
emyPxgcYxn/eR44/KJ4EBs+lVDR3veyJm+kXQ99b21/+jh5Xos1AnX5iItreGCc=
-----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, &lt);
// 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