[ENC28J60] Bài 19: Giao thức MQTT

Giao thức MQTT là giao thức được sử dụng rất nhiều trong thế giới IOT. MQTT có thể chạy trền nền TCP hoặc Socket, do chúng ta mới làm tới TCP nên đương nhiên bài này sẽ hướng dẫn các bạn giao thức TCP trên nền tảng TCP mà chúng ta đã viết từ các bài trước

MQTT cần có 1 broker là trung tâm của mọi luồng dữ liệu, mình sẽ sử dụng broker của trang hive mqtt broker.hivemq.com

Nhà cung cấp này cho trung ta 1 broker công khai miễn phí và không bảo mật do vậy rất phù hợp để học tập và thử nghiệm. MQTT trên nền TCP sẽ được phục vụ tại cổng 1883To

Topic, Publish, Subscribe

Đây là 3 khái niệm cơ bản của MQTT, topic là 1 chủ đề, hiểu 1 cách đơn giản là đích đến của dữ liệu. Publish là hành động xuất bản 1 tin nhắn tới topic nào đó, và Subscriptions – đăng kí nhận dữ liệu từ topic nào đó

Ví dụ: Máy tính đăng kí với broker tôi muốn nhận dữ liệu từ topic cảm biến, mạch thu thập nhiệt độ sẽ thường xuyên gửi dữ liệu nhiệt độ nó đo được lên topic cảm biến. Do máy tính đã đăng kí nhận dữ liệu ở topic nên máy tính sẽ đc broker gửi tin nhắn này cho. Máy tính cũng có thể đăng kí nhận dữ liệu từ các topic khác nữa

Cấu trúc gói

Để làm việc được với MQTT ta cần ít nhất 4 gói tin cơ bản gồm: Gói connect, gói disconect, gói puclish, gói subscribe

Trường Control Header : 1 byte , nó có tác dụng điều hướng gói tin mqtt này thuộc loài nào, với:
Gói connect: 0x10
Gói publish: 0x30
Gói subscribe  : 0x80
Gói unsubscribe  : 0xA0
Gói disconect: 0xE0
Gói CONNACK  : 0x20 ( khi gửi in connect m sẽ check gói này để biết kết nối ok không)
Gói PUBACK  : 0x40 (khi gửi tin nhắn đi mình check gói này để biết bên nhận đã nhận được chưa)
Gói SUBACK : 0x90 (khi gửi tin nhắn đăng kí topic mình check gói này để biết đã đăng kí được chưa)
Gói UNSUBACK  : 0xB0 (khi gửi gói tin hủy đăng kí topic mình check gói này để biết hủy thành công chưa)

Ở byte Control Header , 4 byte đầu là số hiệu của gói, 4 byte sau là các option tùy chọn. Ví dụ = 0 thì không cần phản hồi, bằng 2 là có phản hồi. Cụ thể, bạn gửi gói tin connect với Header là 0x10 thì nó sẽ không phản hồi lại gói CONNACK, còn gửi 0x12 thì nó sẽ gửi lại CONNACK để chắc chắn 100% đã kết nối thành công

Trường Packet Length mô tả phía sau nó còn bao nhiêu byte nữa (không tính bản thân nó)

Trường này không cố định số lượng byte, tối đa 4 và tối thiểu 1. Bit cao nhất của byte sẽ xác định xem byte phía sau nó có thuộc Packet Length không ! Do đó chỉ có 7bit dùng để mã hóa dữ liệu


Ví dụ 1: Gói tin phía sau còn 31 byte thì ta chỉ cần 1 byte cho Packet Length
=> Packet Length = 0x1F

Ví dụ 2: Gói tin phía sau còn 321 byte thì ta sẽ cần 2 byte cho Packet Length với byte1 là byte thấp và byte2 là byte cao. Cứ mỗi giá trị của byte cao sẽ = 128 lần byte thấp. Ta tách 321 = 65 + 2*128

=> Packet Length = 0x41 0x02
Bit cao nhất của byte1 phải được set lên 1 để báo vẫn còn 1 byte nữa cho Packet Length nên phải sửa thành Packet Length = 0xC1 0x02

Với 4 byte Packet Length, ta sẽ mã hóa được tối đa 268,435,455 byte dữ liệu

FromTo
0 (0x00)127 (0x7F)
28 (0x80, 0x01)16 383 (0xFF, 0x7F)
16 384 (0x80, 0x80, 0x01)2 097 151 (0xFF, 0xFF, 0x7F)
2 097 152 (0x80, 0x80, 0x80, 0x01)268 435 455 (0xFF, 0xFF, 0xFF, 0x7F)

Trong bài này, để cho đơn giản mình sẽ mặc định chỉ sử dụng 1 byte cho Packet Length

Tiếp tục tới trường thứ 3 là Variable length Header, nó gồm 4 trường nhỏ sau: Protocol Name, Protocol Level, Connect Flags, and Keep Alive

  • Protocol Name: (6byte) 0x00 0x04 0x4D 0x51 0x54 0x54
  • Protocol Level (version): (1byte) 0x04 = Version: MQTT v3.1.1 (4)
  • Connect Flags: (1 byte) 0x??
  • Keep Alive: (2 byte) 0x?? 0x??

Các bạn chỉ cần quan tâm tới Connect Flags
Cấu trúc của nó như sau:

Bit 1 chúng ta sẽ mặc định để bằng 1
Các bit khác các bạn xài cái nào thì set nó lên. Ví dụ mình không cần user, password thì chỉ cần 0x02 là ok, nếu set cái nào lên thì phải mô tả nó trong payload nhé

Payload là trường sẽ chứa các thông tin để phục vụ việc connect như ID_Client, User, Password. Thông tin trong trường này phải tuân thủ thứ tự sau:
Client ID -> Will Topic -> Will Message -> User Name -> Password

Chú ý: Ở trên byte Connect Flags cái nào không set thì bỏ qua nó trong Payload nhé !

Còn Keep Alive là 2 byte chứa thời gian được tính bằng giây. Đó là khoảng thời gian tối đa được phép trôi qua giữa điểm mà Client hoàn thành việc truyền một Control Packet và điểm mà nó bắt đầu gửi tiếp theo. Nói chung cứ để từ 10 đến 60s tùy các bạn

Tạo thư viện MQTT

Các bạn tạo thêm 2 file mqtt.cmqtt.h cũng như thêm các mã khởi tạo thư viện cơ bản mà chúng ta đã quá quen thuộc

Hàm Connect

Chú ý: Các bạn tạo thêm hàm TCP_request trong thư viện tcp.c nhé

Hàm Publish

2 hàm trên mình sử dụng cấp phát động để khởi tạo bộ nhớ cho struct

Test thử

Chúng ta sẽ kết nối tới broker của hive MQTT và gửi thử 1 vài tin nhắn lên để kiểm tra chương trình hoạt động ok chưa nhé !

Trước tiên trong file main hãy đổi ip server thành 3.127.99.166 vì đó chính là ip của hive mqtt. Mình cũng tạo thêm 1 buff để lưu tin nhắn sẽ gửi và 1 biến i lưu số hiệu tin nhắn

Trong hàm main, trước while(1) mình sẽ gọi hàm MQTT connect với id của client là enc28j60

Và trong main cứ 2s gửi 1 tin nhắn lên topic tên là IOT47

OK, mình sẽ show giao diện debug ở ngay đây

Demo MQTT

Demo gửi nhận MQTT

Tin nhắn gửi tới topic IOT47: ...

Topic:

Tin nhắn:

Mình đã nhận được tin nhắn gửi lên rồi web ! Chúc các bạn thành công 😛

Viết hàm Subscriber

Hàm subscriber

Xử lí các gói tin xác nhận (ACK)

Để chắc chắn việc kết nối, đăng kí thành công 100%, chúng ta sẽ bắt thêm các bản tin ACK nữa nhé, cụ thể mình sẽ bắt gói SUBACK, và CONACK, tuy nhiên mình sẽ để các bạn tự viết thêm chương trình bắt các gói xác nhận này nếu muôn, mình sẽ demo việc bắt gói Publish

Bây giờ mình sẽ viết phương thức MQTT_read để bên thư viện TCP gọi nó ra. Nó giống như việc thằng TCP nhận đc 1 gói tin nhìn giống giống gói MQTT nên sẽ gọi hàm MQTT_read và bảo "ê, có gói tin na ná mqtt này, tự đi mà xử lí tau không quan tâm"

Quay lại thư viện TCP, mở tcp.h và add thư viện mqtt.h

Ở cuối hàm TCP_readData thêm phần kiểm tra xem có gói tin mqtt nào không rồi gọi hàm MQTT_read cho em nó xử lí

OK ! bây giờ quay lại hàm MQTT_read và chúng ta sẽ xử lí data ở đó

Trước khi viết tiếp mình sẽ test thử xem có đúng là đã nhận được bản tin publish nào chưa đã

Quay về, hàm main, sau khi connect tới broker xong, gọi hàm MQTT_subscriber

Và ở trang này các bạn cuộn lên giao diện MQTT debug mình đã viết bên trên, thử gửi 1 tin nhắn tới topic iot47_hehe xem

Đăng kí call back nhận tin nhắn

Các bạn có thể viết code thực thi gì đó ở hàm MQTT read luôn, nhưng để độc lập các thư viện mình sẽ tạo hàm callback để có thể viết hàm xử lí ở file main. Mình sẽ tạo 1 con trỏ hàm để trỏ tới hàm cần cần gọi, các bạn mở file mqtt.c và khởi tạo 1 con trỏ hàm ở đầu file

Và tạo thêm hàm đăng kí callback

Khi chúng ta gọi hàm này và nạp vào địa chỉ của hàm cần callback, biến toàn cục mqtt_function sẽ lưu lại địa chỉ của hàm đó

Bây giờ, trong hàm MQTT_read, tiến hành gọi hàm callback

Hàm MQTT_read lúc này như sau:

Bây giờ các bạn có thể quay về file main, tạo 1 hàm với 1 tên bất kì, ví dụ

Hàm này nhập vào con trỏ struct chứa các thông tin topic, mess để các bạn so sánh hay xử lí gì thì tùy

Sau đó, sau khi kết nối tới broker, gọi
MQTT_registerCallback(&mqtt_callback);

Lúc này, mỗi khi có tin nhắn gửi tới, hàm mqtt_callback sẽ tự động được gọi

Callback là 1 cách rất hay ho để tăng tính độc lập giữa các thư viện

Gói ping

Chúng ta phải thường xuyên ping tới broker để nó biết client vẫn đang hoạt động, khi gửi ping request thì broker cũng sẽ gửi ping reponse. Vậy là cả 2 thằng đều biết là cả 2 vẫn hoạt động bình thường

Thời gian ping phải nhỏ hớn thời gian keep alive, nếu không broker sẽ ngắt kết nối và cho rằng client đã offlife. Lúc này broker sẽ phát đi 1 tin nhắn tới cho các client khác trong mạng đã đăng kí topic bảo rằng thằng này đã offline (đương nhiên muốn làm thế thì client phải chủ động thỏa thuận với broker topic sẽ nhắn và nội dung sẽ nhắn khi tôi offline), hiểu nôm na như để lại di chúc vậy

Do vậy, MQTT có khái niệm will retain

< mình sẽ cập nhật hàm đăng kí gói tin này sau> trong phần này sẽ tập trung viết gói ping

Gói Disconect

 

Từ tác giả:

Nếu có bất kì thắc mắc nào trong bài viết, vui lòng để lại comment dưới mỗi bài ! Mình sẽ không trả lời thắc mắc của các bạn ở facebook hay email !

Nếu trong phần code bạn nhìn thấy nhưng thứ kiểu như &amp; thì đó là lỗi hiển thị, cụ thể 3 kí tự < > & bị biến đổi thành như thế
&amp; là &
&lt;  là <
&gt; là >

Giới thiệu Đào Nguyện 80 bài viết
DIY,chế cháo, viết blog chia sẽ kiến thức về lập trình,điện tử - IoT. Rất mong được giao lưu, kết bạn với các bạn cùng đam mê. Địa chỉ Facebook: https://www.facebook.com/nguyendao207

2 bình luận

  1. Chào anh, em có tham khảo bài viết này của anh về MQTT nhưng với thingspeak. Kết quả lại không hiển thị được dữ liệu, địa chỉ IP broker em lấy là địa chỉ IP thingspeak server như với giao thức HTTP không biết có đúng không ?

    • địa chỉ IP thì chỉ có 1 thôi đương nhiên nó sẽ đúng rồi. nhưng các giao thức phân biệt với nhau bằng cổng ! bạn đã chọn đúng cổng chưa ? thông thường MQTT TCP hoạt động ở công 1883, MQTT socket chạy ở cổng 9000 ! Mà thingspeak có mqtt broker à, lần đầu nghe thấy đó

Đã đóng bình luận.