Phần 9: Điều khiển thiết bị qua Cloud server với ESP8266 và MQTT

WearableTiếp tục với chủ đề sử dụng ESP8266 trong các ứng dụng Internet Of Things, trong phần này chúng ta sẽ thảo luận mô hình cloud/server rất phổ biến hiện nay để kết nối và điều khiển nhiều thiết bị IoT cùng lúc. Hiện tại thì MQTT được sử dụng khá phổ biến do tính linh hoạt và đã được chuẩn hóa và sử dụng bởi nhiều nhà cung cấp giải pháp IoT

1/ Giới thiệu giao thức MQTT

MQTT là 1 giao thức truyền dữ liệu giữa nhiều thiết bị thông qua 1 trạm trung gian (gọi là Broker – là 1 application chạy trên máy tính server/cloud).

Về cơ bản, các thiết bị IoT sẽ đóng vai trò client và đăng ký với Broker dữ liệu nó muốn gửi lên hoặc dữ liệu nó muốn nhận được. Nhiệm vụ của Broker là thiết bị trung gian nhận dữ liệu truyền lên từ các thiết bị client và gửi dữ liệu đến các client muốn nhận tương ứng. Chúng ta có thể hình dung mô hình Broker – Client tương tự như mô hình tổng đài điện thoại. Lưu ý là MQTT không phải là mô hình Server-Client vì tất cả thiết bị đều ngang hàng và đều có thể gửi dữ liệu lẫn nhau thông qua cầu nối Broker.

Các thiết bị client có thể đăng ký nhận dữ liệu hoặc ngừng nhận dữ liệu từ Broker trong runtime tùy yêu cầu của ứng dụng. Ngoài ra các thiết bị client gửi dữ liệu cũng không cần đăng ký trước loại dữ liệu mà nó sẽ gửi. Do đó, có thể nói giao thức MQTT là mô hình sao và cho phép nhiều thiết bị trao đổi dữ liệu khá linh hoạt cả đối với thiết bị gửi dữ liệu và thiết bị nhận dữ liệu.

Flow làm việc của giao thức MQTT có thể được mô tả 1 cách đơn giản như sau:

  • Kết nối đến 1 Broker
  • Đăng ký loại dữ liệu muốn nhận (nếu thiết bị có nhu cầu nhận dữ liệu)
  • Gửi dữ liệu lên Broker (khi thiết bị có dữ liệu và muốn gửi tới các thiết bị khác)

          mqtt_star    mqtt_model

Các khái niệm cơ bản của MQTT

Sau khi nắm được nguyên lý của giao thức MQTT, chúng ta sẽ giới thiệu các khái niêm cơ bản của MQTT như sau:

  • Client: là thiết bị IoT muốn gửi/nhận dữ liệu trong network
  • Broker: là thiết bị trung gian nhận dữ liệu từ các client muốn gửi và gửi dữ liệu đó tới các client muốn nhận
  • Topic: tượng trưng cho loại dữ liệu mà các thiết bị Client gửi/nhận thông qua MQTT. Topic là 1 chuỗi UTF-8 text tượng trưng cho tên loại dữ liệu.
    Ví dụ của topic như: “example/topic1”,  “demo_esp/sensor/accel”, “demo_esp/sensor/gyro”, v.v….
    Trong topic, chúng ta có thể phân chia cấp dùng ký từ ‘/’  và từ đó có thể đăng ký nhận nhiều loại dữ liệu dùng các ký tự đặc biệt như ‘#’ hoặc ‘+’ như sau:
    ‘+’  Đăng ký nhiều topic chỉ khác nhau 1 vị trí phân cấp trong text của topic. Ví dụ như “demo/sensor/+” sẽ đăng ký nhận dữ liệu từ các topic như “demo/sensor/accel”, “demo/sensor/gyro”, v.v…
    ‘#’  Đăng ký nhiều topic có khác nhau nhiều vị trí phân cấp trong text của topic. Ví dụ như “demo/*” sẽ đăng ký nhận dữ liệu từ tất cả topic bắt đầu bằng “demo/”
  • Publish: là bước gửi dữ liệu từ 1 thiết bị client đến Broker. Trong bước này, thiết bị IoT sẽ xác định topic (là loại dữ liệu muốn gửi) và giá trị của topic đó
  • Subscribe: là bước đăng ký nhận dữ liệu từ Broker của 1 thiết bị Client. Trong bước này, thiết bị IoT sẽ xác định loại dữ liệu mà nó muốn nhận. Khi Broker nhận được loại dữ liệu này từ 1 thiết bị client khác, nó sẽ gửi dữ liệu này tới thiết bị client đã đăng ký nhận
  • Unsubscribe: là bước thông báo với Broker là thiết bị Client không muốn tiếp tục nhận dữ liệu nữa

Sau khi hiểu được các khái niệm cơ bản nhưng quan trọng này, các bạn có thể sử dụng các thư viện open-source cho MQTT client và MQTT Broker vì flow chương trình sử dụng thư viện MQTT client khá đơn giản chỉ sử dụng vài hàm API như sau:

  • Tạo kết nối TCP đến server chạy code MQTT Broker
  • Gọi hàm publish() để gửi dữ liệu lên Broker
  • Gọi hàm subscribe() để đăng ký nhận dữ liệu từ Broker (hoặc unsubscribe() để ngừng nhận dữ liệu từ Broker)
  • Đăng ký hàm callback của ứng dụng để được gọi khi thư viện MQTT client nhận được dữ liệu từ Broker

Với phạm vi và thời gian hạn chế,  bài viết muốn giới thiệu cách thức hoạt động của giao thức MQTT và các kiến thức tổng quát đủ để các bạn hiểu được bản chất của MQTT và sử dụng các thư viện open-source cho MQTT trong các ứng dụng của mình. Nếu các bạn muốn tìm hiều sâu hơn và đầy đủ hơn cách thức hoạt động của giao thức MQTT để hiểu source code của các bộ thư viện hoặc tự viết 1 bộ thư viện cho riêng mình, các bạn có thể tham khảo các trang web giải thích chi tiết hoặc tài liệu specification của MQTT  ở đường link này http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.pdf

2/ Sử dụng MQTT với ESP8266

Với ESP8266, các thiết bị IoT có thể dễ dàng kết nối đến TCP socket của 1 MQTT Broker từ đó mở ra khả năng trao đổi dữ liệu giữa hàng ngàn thiết bị IoT với nhau qua mô hình cloud server.

Hiện nay, có khá nhiều thư viện open-source chúng ta có thể sử dụng cho dự án của mình 1 cách nhanh chóng mà không cần thời gian đọc hiểu chi tiết giao thức MQTT. Ví dụ các thư viện open source phổ biến như sau:

Mỗi thư viện đều có tài liệu và sample code hướng dẫn cách sử dụng các hàm API cho hành động kết nối server, publish(), subscribe(), unsubscribe() và đăng ký callback. Do đó  chúng ta dễ dàng tích hợp khả năng truyền dữ liệu qua cloud server giữa các thiết bị ESP8266 1 cách nhanh chóng và dễ dàng với MQTT nhờ sự giúp đỡ của cộng đồng phát triển các bộ thư viện open source này. Trong bài viết này, bài viết sẽ giới thiệu cách sử dụng thư viện PubSubClient vì nó khá đơn giản và dễ hiểu với nhiều người.

Sử dụng các hàm API của thư viện PubSubClient hỗ trợ chức năng MQTT Client

Các hàm API cơ bản và thông dụng nhất của thư viện PubSubClient có thể được liệt kê như sau:

  •  Hàm PubSubClient(Client &client) để khởi động bộ thư viện. Chúng ta sẽ tạo 1 biến kiểu Client và pass địa chỉ biến đó vào hàm này để khởi động bộ thư viện.
  • Hàm setServer(<domain name của Broker>, <TCP port>) để thiết lập địa chỉ của Broker server và port TCP sẽ kết nối đến
  • Hàm  setCallback(<callback>) để đăng ký hàm callback sẽ được gọi khi thư viện nhận được giá trị mới từ Broker cho dữ liệu đã được subscribe
  • Hàm connect(<client_name>) để bắt đầu quá trình kết nối đến Broker server. Hàm này sẽ đợi đến khi kết nối thành công hoặc timeout; giá trị trả về của hàm là kiểu boolean với True là kết nối thành công, False là kết nối không thành công
  • Hàm disconnect() để chủ động ngắt kết nối đến Broker server
  • Hàm publish(<topic>, <value>) để gửi giá trị <value> cho dữ liệu <topic> lên Broker server
  • Hàm subscribe(<topic>) để đăng ký nhận giá trị mới của dữ liệu <topic> từ Broker server
  • Hàm connnected() để kiểm tra trạng thái kết nối đến Broker server (True = kết nối OK)
  • Hàm loop() là hàm xử lý các tác vụ theo giao thức MQTT. Chúng ta sẽ gọi hàm này liên tục trong hàm loop() của Arduino

Để cài đặt thư viện PubSubClient trên Arduino, chúng ta vào Sketch -> Include Library -> Manage Libraries….  rồi chọn cài đặt PubSubClient của Nick O’Leary. Các file thư viện sau khi cài đặt có thể tìm ở C:\Users\<name>\Arduino\libraries\PubSubClient\ . Các bạn có thể tìm hiểu cách thức hiện thực MQTT client bằng cách tìm hiểu source code trong thư mục PubSubClient này.

Để demo quá trình sử dụng các thư viện này trong 1 ứng dụng IoT sử dụng ESP8266, chúng ta lập trình 2 thiết bị để demo quá trình truyền nhận dữ liệu với mô hình cloud server với MQTT trong phần tiếp theo.

3/ Demo điều khiển thiết bị qua Internet với MQTT và ESP8266

Trong phần này chúng ta sẽ lập trình 2 module ESP-01: 1 module đóng vai trò thiết bị điều khiển, 1 module đóng vai trò thiết bị nhận lệnh; để demo quá trình điều khiển thiết bị IoT qua cloud server.

Chúng ta sẽ sử dụng Broker server miễn phí là test.mosquitto.org, đây là Broker server miễn phí và công cộng để hỗ trợ test nhanh hoạt động của MQTT client. Trên server này đã chạy sẵn 1 MQTT Broker với các port TCP như:

  • 1883 : kết nối không mã hóa
  • 8883 : Kết nối có mã hóa
  • 8884 : Kết nối có mã hóa và yêu cầu thông tin đăng nhập
  • 8080 : dùng với WebSocket và không mã hóa
  • 8081 : dùng với WebSocket và có mã hóa

Với demo này, chúng ta sẽ sử dụng TCP port 1883. Lưu ý đây là server public nên dữ liệu chúng ta gửi lên người khác có thể xem được (nếu họ biết chuỗi text của “topic” của mình)

Hardware cần chuẩn bị

Chúng ta sẽ sử dụng 2 module ESP-01 và 1 module FT232 để nạp chương trình như hình sau:

load_fw

Software cần chuẩn bị

  • Thư viện cho ESP8266 trên Arduino (đã giới thiệu ở bài 7)
  • Thư viện PubSubClient trên Arduino

Lập trình module ESP-01 Device

Với module ESP-01 đóng vai trò Device, flow chương trình sẽ như sau:

  • Gọi hàm PubSubClient() để khởi động thư viện PubSubClient
WiFiClient espClient;
PubSubClient client(espClient);
  • Gọi hàm setServer() để đăng ký Borker server test.mosquitto.org và TCP port 1883
client.setServer(“test.mosquitto.org”, 1883);
  • Gọi hàm setCallback() để đăng ký hàm callback để xử lý dữ liệu nhận được từ Broker
client.setCallback(mqtt_callback);
  • Và định nghĩa hàm callback này
void mqtt_callback(char* topic, byte* payload, unsigned int length) {
//topic là chuỗi text của loại dữ liệu đã subscribe
//payload là buffer chứa giá trị của dữ liệu
// length là size của buffer payload
}
  • Gọi hàm subscribe() để đăng ký nhận dữ liệu từ Broker
client.subscribe(“smarthome/room_1/command_1”);
client.subscribe(“smarthome/room_1/command_2”);
  • Trong hàm loop() của Arduino, kiểm tra kết nối đến Broker server với hàm connected(), nếu chưa kết nối thành công thì gọi hàm connect(). Sau khi hàm connect() thành công, gọi hàm loop() của PubSubClient để xử lý các sự kiện của MQTT client trong quá trình làm việc
if (!client.connected())
{
client.connect(“ESP8266_MQTT_Demo”)
}
client.loop()

Thế là xong, chúng ta chỉ cần thêm code ứng dụng trong hàm mqtt_callback() để chờ nhận lệnh từ thiết bị Controller để thực hiện chức năng cụ thể như tắt/mở đèn trong nhà, kiểm tra trạng thái cảm biến, v.v…

Lập trình module ESP-01 Controller

Flow chương trình cũng tương tự như của ESP-01 Device, ngoại trừ chúng ta sẽ không cần đăng ký hàm callback, cũng như sử dụng hàm subscribe() để đăng ký nhận dữ liệu từ Broker. Thay vào đó, chúng ta sẽ gọi hàm publish() để gửi lệnh điều khiển tới ESP-01 Device.

Trong demo này, hàm Arduiono loop() sẽ gửi giá trị của dữ liệu “smarthome/room_1/command_1” là giá trị của 1 counter (thời gian chu kỳ là 1s) và giá trị của dữ liệu “smarthome/room_1/command_2” là giá trị của 1 counter (thời gian chu kỳ là 5s). Do đó trong hàm Arduino loop() sẽ cập nhật giá trị 2 counter này và gửi giá trị lên Broker để gửi đến module ESP-01 Device. Khi module ESP-01 Device nhận được giá trị mới của “smarthome/room_1/command_1” hoặc “”smarthome/room_1/command_2”, nó sẽ print ra Serial window để chúng ta quan sát.

if (!client.connected())
{
client.connect(“ESP8266_MQTT_Demo”)
}
client.loop()
long now = millis();
if (now – timer1 > 1000) {
counter1++;
timer1 = now;
client.publish(“smarthome/room_1/command_1”, String(counter1).c_str(), true);
}
if(now – timer2 > 5000) {
counter2++;
timer2 = now;
client.publish(“smarthome/room_1/command_2”, String(counter2).c_str(), true);
}

File sketch của ESP-01 Controller và ESP-01 Device.

Với demo này, thiết bị Controller chỉ gửi dữ liệu mà không nhận trạng thái trả về từ thiết bị Device. Trong ứng dụng thực tế, các bạn có thể lập trình thiết bị Controller cũng gọi hàm subscribe() và code hàm callback() để có thể nhận trạng thái trả về từ thiết bị Device tương tự như trên.

Bài viết đến đây là hết rồi. Hy vọng các bạn đã hiểu được khái quát giao thức MQTT, cách sử dụng các thư viện open-source cho MQTT client có thể sử dụng cho các ứng dụng IoT với chip ESP8266. Với mô hình này, chúng ta có thể dễ dàng thiết lập 1 hệ thống cloud server (chạy code MQTT Broker) và tạo 1 network nhiều thiết bị client gửi/nhận dữ liệu với cloud server 1 cách dễ dàng và nhanh chóng và việc tích hợp thêm thiết bị hơặc gửi thêm dữ liệu mới cũng không yêu cầu cập nhật lại nhiều thành phần của hệ thống. Đây là giải pháp tối ưu để điều khiển/giám sát nhiều thiết bị cùng lúc, và có thể ở nhiều vị trí khác nhau