Lập trình ESP32 với mạng WiFi Mesh (painlessMesh)

Lập trình ESP32 với mạng WiFi Mesh

Đây là bài cuối cùng trong nhóm Giao thức của serie — chúng ta tìm hiểu WiFi Mesh, kiểu mạng không cần router trung tâm, các node ESP32 tự kết nối và chuyển tiếp dữ liệu cho nhau. Bài này phức tạp hơn các bài trước nên đặt ở cuối, và cần từ 2 board ESP32 trở lên để thấy rõ hiệu quả (càng nhiều board càng thể hiện rõ khả năng “tự vá” của mesh).

Đây là một bài thực hành trong Serie Lập trình ESP32 từ A tới Z.

WiFi Mesh khác WiFi Station/AP ở đâu?

Toàn bộ các bài WiFi trước trong serie đều theo mô hình hình sao (star): có 1 router trung tâm, mọi thiết bị kết nối trực tiếp tới router đó. Nếu 2 thiết bị ở quá xa router, chúng không nói chuyện được với nhau.

Mesh (lưới) thì khác — mỗi node vừa là “khách” vừa là “chủ nhà” cùng lúc: nếu node A không với tới node C, nhưng node B nằm giữa với tới cả A và C, thì B sẽ tự động chuyển tiếp (relay) dữ liệu giúp A và C nói chuyện được với nhau, dù chúng chưa từng kết nối trực tiếp. Mạng mesh còn có khả năng tự vá (self-healing) — nếu 1 node giữa đường bị tắt, mesh tự tìm đường khác qua node còn lại.

So sánh mạng hình sao qua router với mạng WiFi Mesh painlessMesh: node B chuyển tiếp tin nhắn từ node A tới node C, mạng tự vá khi 1 node tắt

Lưu ý: Tự viết code quản lý mesh từ đầu (dùng thẳng esp_mesh.h của ESP-IDF) rất phức tạp, đòi hỏi hiểu sâu về giao thức mạng. Bài này dùng thư viện bên thứ 3 painlessMesh — đúng như tên gọi, lo hết phần khó (định tuyến, tự vá, đồng bộ thời gian giữa các node), code của bạn chỉ cần gọi vài hàm đơn giản để gửi/nhận tin nhắn.

Lập trình mạng WiFi Mesh

Khởi tạo node tham gia mesh

#define MESH_PREFIX     "ESP32MeshNetwork"
#define MESH_PASSWORD   "MeshPassword123"
#define MESH_PORT       5555

painlessMesh mesh;

mesh.init(MESH_PREFIX, MESH_PASSWORD, &userScheduler, MESH_PORT);
mesh.onReceive(&receivedCallback);

MESH_PREFIX/MESH_PASSWORD giống như “tên WiFi” và “mật khẩu” của cả mạng mesh — mọi node phải khai đúng y hệt nhau thì mới tự tìm thấy và ghép nối được với nhau. Khác hẳn Station/AP chỉ có 1 access point cố định, ở đây bất kỳ node nào bật lên cùng MESH_PREFIX sẽ tự động gia nhập mạng, không cần chỉ định ai là “trung tâm”.

Gửi và nhận tin nhắn qua toàn mạng

void sendHeartbeat() {
  String message = "Xin chao tu node: " + String(mesh.getNodeId());
  mesh.sendBroadcast(message);
}

void receivedCallback(uint32_t from, String &message) {
  Serial.printf("Nhan tu node %u: %s\n", from, message.c_str());

  if (message == "LED_ON") {
    digitalWrite(ledPin, HIGH);
  }
}

mesh.sendBroadcast() gửi 1 tin nhắn tới toàn bộ node đang có trong mạng — không cần biết trước có bao nhiêu node hay chúng ở đâu, painlessMesh tự lo việc chuyển tiếp qua các node trung gian nếu cần. receivedCallback() được gọi tự động mỗi khi có tin nhắn tới, kèm from cho biết tin nhắn gửi từ node nào (mỗi node có 1 nodeId số nguyên duy nhất, tự sinh ra khi khởi động).

Lưu ý: Hàm mesh.update() phải được gọi liên tục trong loop(), không được chặn (block) bởi delay() dài hay vòng while chờ đợi — đây là nơi painlessMesh xử lý ngầm toàn bộ việc định tuyến, đồng bộ thời gian, phát hiện node mới/mất. Nếu loop() bị treo bởi 1 đoạn code chờ lâu, node đó sẽ mất kết nối khỏi mesh.

Code đầy đủ

Nạp y hệt đoạn code này vào TẤT CẢ các board bạn muốn đưa vào mạng mesh:

#include <Arduino.h>

#include <painlessMesh.h>

#define MESH_PREFIX     "ESP32MeshNetwork"
#define MESH_PASSWORD   "MeshPassword123"
#define MESH_PORT       5555

const int ledPin = 26;

painlessMesh mesh;
Scheduler userScheduler;

void sendHeartbeat();
Task taskSendHeartbeat(TASK_SECOND * 5, TASK_FOREVER, &sendHeartbeat);

void sendHeartbeat() {
  String message = "Xin chao tu node: " + String(mesh.getNodeId());
  mesh.sendBroadcast(message);
  Serial.println("Da gui: " + message);
}

void receivedCallback(uint32_t from, String &message) {
  Serial.printf("Nhan tu node %u: %s\n", from, message.c_str());

  if (message == "LED_ON") {
    digitalWrite(ledPin, HIGH);
  } else if (message == "LED_OFF") {
    digitalWrite(ledPin, LOW);
  }
}

void newConnectionCallback(uint32_t nodeId) {
  Serial.printf("Co node moi tham gia mang mesh: %u\n", nodeId);
}

void changedConnectionCallback() {
  Serial.println("Cau truc mang mesh vua thay doi (node them/mat ket noi)");
}

void setup() {
  Serial.begin(115200);
  pinMode(ledPin, OUTPUT);

  mesh.setDebugMsgTypes(ERROR | STARTUP);
  mesh.init(MESH_PREFIX, MESH_PASSWORD, &userScheduler, MESH_PORT);

  mesh.onReceive(&receivedCallback);
  mesh.onNewConnection(&newConnectionCallback);
  mesh.onChangedConnections(&changedConnectionCallback);

  userScheduler.addTask(taskSendHeartbeat);
  taskSendHeartbeat.enable();

  Serial.print("Node ID cua board nay: ");
  Serial.println(mesh.getNodeId());
}

void loop() {
  mesh.update();
}

Code đầy đủ mình để trên GitHub, các bạn xem tại 04-Protocol/08_WiFi_Mesh nhé.

Kết quả

Nạp cùng 1 đoạn code này vào ít nhất 2 board ESP32 (càng nhiều board càng rõ hiệu ứng mesh), mở Serial Monitor của từng board.

Log Serial Monitor của node 1:

Log Serial Monitor node 1 ESP32 painlessMesh phát hiện node mới và nhận tin nhắn broadcast

Log Serial Monitor của node 2:

Log Serial Monitor node 2 ESP32 painlessMesh gửi và nhận tin nhắn broadcast

Sau vài giây, mỗi board sẽ tự in ra “Có node mới tham gia mạng mesh” khi phát hiện board kia — không cần cấu hình ai kết nối với ai. Mỗi 5 giây, các board gửi tin nhắn broadcast cho nhau và bạn sẽ thấy log nhận được trên Serial Monitor của board còn lại, đúng không nào. Thử tắt 1 board giữa chừng (nếu có từ 3 board trở lên xếp thành chuỗi), các board còn lại vẫn tự tìm đường kết nối lại với nhau.

Kết

WiFi Mesh mở ra khả năng phủ sóng những khu vực rộng mà 1 router đơn không với tới — nhà nhiều tầng, khu vườn rộng, hệ thống cảm biến rải rác trong nhà xưởng. Đây cũng là bài khép lại nhóm Giao thức của serie — từ HTTP, HTTPS, TCP, UDP, ESP-NOW cho tới Mesh này, bạn đã có đủ bộ công cụ giao tiếp mạng để chọn đúng giao thức cho từng tình huống thực tế.

Nếu thấy bài viết có ích, hãy chia sẻ cho bạn bè cùng học. Có thắc mắc gì cứ để lại bình luận, mình sẽ giải đáp nhé!

Rate this post

Để lại một bình luận

Email của bạn sẽ không được hiển thị công khai. Các trường bắt buộc được đánh dấu *