| คำถาม | คำตอบของชุดบทเรียนนี้ | อยู่ช่วงไหน | |
|---|---|---|---|
| Why | บทเรียน 4.1–4.3 บอร์ดมี IP และ ping ออกไปได้แล้ว ทำไมยังไม่พอ | เพราะ ping พิสูจน์ได้แค่ว่าสายดี ยังไม่มีใครที่ปลายทางรับข้อมูลของเรา · และของที่ส่งออกได้อย่างเดียวยังไม่เรียกว่าระบบ ระบบต้องรับคำสั่งกลับมาได้ด้วย · ชื่อ topic กับรูปร่าง payload คือ สัญญากับทุกคนที่จะใช้ข้อมูลนี้ต่อ เปลี่ยนทีหลังแปลว่าพังทุกฝั่งพร้อมกัน | ครึ่งแรก · pub/sub · topic และ payload |
| What | มีอะไรให้ใช้บ้าง | โมดูล mqtt มีหกชื่อ เท่านี้จริง ๆ — และเพดานสี่ข้อที่ต้องจำคู่กันไป: ช่องรับ 1 ข้อความ · payload 255 ไบต์ · topic 127 ไบต์ · client_id 31 ตัวอักษร |
สไลด์บัญชีหกชื่อ + สไลด์ get_message() ช่องเดียว |
| How | ประกอบยังไงให้ใช้งานได้จริง | ต่อ WiFi → ต่อ broker → publish() ค่าจริงทุก 5 วินาที โดยที่ลูปเดียวกันยังฟัง get_message() อยู่ แล้วเอาคำสั่งที่รับมาสั่ง LED บนบอร์ด |
แปดไฟล์ตัวอย่าง + ไฟล์ฝึก + ติดตั้ง CE ด้วย Docker |
ปลายทางที่จับต้องได้ — MQTT Explorer บนคอมเห็นค่าจากบอร์ดทุก 5 วินาที และเราพิมพ์คำสั่งจากคอมกลับไปให้ LED บนบอร์ดสลับสถานะได้
บทเรียน 4.1–4.3 เราพิสูจน์ว่าสายดี · ชุดบทเรียนนี้เราหาคนที่ปลายสายเจอ และคุยกันได้สองทาง
mqtt.connect() / publish() / subscribe() / get_message() ได้ถูกต้องตามข้อจำกัดจริงของเฟิร์มแวร์ปลายทางของวันนี้: MQTT Explorer เห็นค่าจากบอร์ดทุก 5 วินาที และเราพิมพ์คำสั่งจากคอมให้ LED บนบอร์ดสลับสถานะได้
ชุดบทเรียนนี้เป็นบทเรียนแรกที่ข้อมูลของเรา ออกจากบอร์ด ไปอยู่ในมือของโปรแกรมอื่น

03_connect_and_publish.py บน BENTO Emulator ที่ 800x480 เท่าจอของทั้งสองบอร์ด — ไม่ใช่ภาพวาด ไม่ใช่ mock-up และไม่ใช่ภาพถ่ายจากบอร์ด · หน้าจอของไฟล์เฉลย (ไฟสี่ดวง · ลูกบิด · สามใบล่าสุด · ปุ่มเริ่ม/หยุดส่ง) ยังไม่ได้ถ่ายถ้าจอบอกว่าส่งแล้ว แต่ฝั่งรับไม่เห็นอะไร แปลว่ายังไม่จบ — ชุดบทเรียนนี้ต้องดูสองจอพร้อมกัน
สองอย่างจากชุดบทเรียนก่อนหน้าที่ต้องเอามาใช้วันนี้ทั้งคู่ — wifi.connect() บล็อกได้นานถึงราว 85 วินาทีถ้ารหัสผิด จึงต้องต่อ WiFi ให้ได้ก่อนเสมอแล้วค่อยเริ่มเรื่อง MQTT และ wifi.ping() รับเฉพาะหมายเลข IP ไม่รับชื่อโฮสต์ — เดี๋ยวเราจะใช้มันตรวจว่าเครื่องที่รัน broker อยู่ในระยะที่คุยได้จริงก่อนจะเสียเวลาต่อ
ถ้า
wifi.is_connected()เป็น False อย่าเพิ่งไปหาสาเหตุที่ MQTT — ปัญหายังไม่เดินทางมาถึงชั้นนั้น
ซ้าย คือแบบที่เราคุ้น (client–server) เว็บเบราว์เซอร์ต้องรู้ชื่อเซิร์ฟเวอร์ ต้องถามก่อนถึงจะได้คำตอบ ผู้ถามกับผู้ตอบ ผูกกันตรง ๆ ถ้าอีกฝั่งย้ายที่อยู่ ทุกฝั่งต้องแก้ตาม
ขวา คือ publish/subscribe ผู้เขียน (writer) โยนข้อมูลใส่ ชื่อเรื่อง ผู้อ่าน (reader) ขอรับตาม ชื่อเรื่อง — ทั้งสองฝั่งไม่เคยรู้จักกัน รู้จักแค่ชื่อเรื่องเดียวกัน
ผลที่ตามมาสามข้อ ซึ่งเป็นเหตุผลที่ IoT เลือกแบบหลัง
คำที่ต้องจำ: pub/sub แยกผู้ส่งออกจากผู้รับ ทั้งในเชิงพื้นที่และเชิงเวลา

CONNECT เข้าไปแนะนำตัว → SUBSCRIBE ขอรับ topic ที่สนใจ → PUBLISH ส่งข้อมูล → DISCONNECT บอกลา
สี่คำนี้คือทั้งหมดที่ผู้เรียนต้องใช้วันนี้ ที่เหลือ broker จัดการให้เอง
อะไรคือ MQTT ? — code maow maow | โค้ดแมวแมว · 8 นาที 26 วินาที · ไทย — ดูเพื่อเห็นภาพรวมของ broker/publisher/subscriber เป็นภาษาไทยก่อนลงมือ
broker ไม่ใช่ฐานข้อมูล มันคือ ที่ทำการไปรษณีย์ — รับเข้ามาแล้วส่งต่อทันที ไม่เก็บไว้ให้ (เว้นแต่สั่งให้เก็บ)
ในภาพ ปลั๊กสองตัวส่ง /plug1/voltage, /plug2/current ส่วนแล็ปท็อป subscribe /plug1/# และมือถือ subscribe /+/current
# = ทุกอย่างที่อยู่ใต้ลงไปกี่ชั้นก็ได้ (ต้องเป็นตัวสุดท้าย) · + = แทนที่ หนึ่งชั้นพอดี
MQTT Essentials Part 6 — Topic Best Practices — HiveMQ · 5 นาที 50 วินาที · อังกฤษ — ดูเพื่อเข้าใจว่าทำไม topic ที่ออกแบบดีทำให้ระบบขยายได้โดยไม่ต้องแก้โค้ดฝั่งอุปกรณ์
topic ไม่ต้องประกาศล่วงหน้า — พิมพ์ publish ไปที่ชื่อไหน ชื่อนั้นก็เกิดขึ้นเดี๋ยวนั้น จึงต้องมีวินัยตั้งชื่อเอง
ทั้ง WiFi และ MQTT ทำงานอยู่บน CM33_NS คอร์เดียวกับที่รันโค้ด Python ของเรา ส่วน CM55 ที่วาดจอกับอ่านเซนเซอร์ไม่แตะเครือข่ายเลย — ทั้งสองบอร์ดจงใจอย่างนั้น เพราะสองคอร์แย่งอุปกรณ์ตัวเดียวกันเคยทำให้เครื่องล้มมาแล้ว · ภาพนี้จริงทั้ง Eva Kit และ Dev Kit เพราะโค้ด Wi-Fi/MQTT เป็นชุดเดียวกัน ต่างกันแค่ใครอ่าน IMU (Eva: คอร์จออ่านให้ · Dev Kit: CM33 อ่านเองจาก I2C) · เหตุผลที่ชุดบทเรียนนี้ใช้ 1883 ไม่ใช่เรื่องหน่วยความจำ แต่เพราะโมดูล mqtt ส่งข้อมูลรับรอง TLS เป็นค่าว่างเสมอ (ดูตารางหกชื่อ) จึงต่อได้เฉพาะพอร์ตข้อความเปล่าบนบอร์ดไหนก็ตาม
ข้อที่ต้องจำให้แม่น: งานเครือข่ายวางข้อความขาเข้าลงช่องรับ "เมื่อไรก็ได้" ไม่ได้รอให้โค้ดเราเรียก get_message() ก่อน — สไลด์ถัดไปทั้งสไลด์มาจากข้อเท็จจริงข้อนี้
เครือข่ายกับหน้าจออยู่คนละคอร์ — ถ้าจอค้าง MQTT อาจยังวิ่งอยู่ และในทางกลับกันด้วย
ดูแถวบนสุดของภาพ: 16 บิต = 2 ไบต์ นั่นคือส่วนหัวคงที่ของ MQTT ทั้งหมด — ชนิดแพ็กเก็ต, ธง DUP, ระดับ QoS, ธง RETAIN และความยาว อัดอยู่ในสองไบต์นั้น
เทียบกับ HTTP ที่ส่วนหัวเป็นข้อความยาวหลายร้อยไบต์ต่อคำขอหนึ่งครั้ง ("GET /… HTTP/1.1", "Host:", "User-Agent:", …) ความต่างนี้ไม่ใช่เรื่องความสวยงาม แต่คือ ค่าไฟกับค่าเน็ตของอุปกรณ์ที่ต้องส่งข้อมูลทุกห้าวินาที เป็นปี
มาตรฐานเองก็ไม่ได้เกิดในวันเดียว: MQTT 3.1.1 ถูกอนุมัติเป็นมาตรฐาน OASIS เมื่อ 2014-10-29 และ 5.0 เมื่อ 2019-03-07 เวอร์ชันที่อุปกรณ์ฝังตัวใช้กันแพร่หลายที่สุดจนถึงวันนี้ยังเป็น 3.1.1
เชื่อมกับวันนี้: payload ของเราจะยาวราว 80 ไบต์ ส่วนหัว MQTT กินเพิ่มแค่หลักหน่วยไบต์ — ถ้าเราเลือก HTTP แทน ข้อมูลจริงจะกลายเป็นส่วนน้อยของสิ่งที่ส่งออกไป และแบตเตอรี่จะหมดไปกับการส่ง "ชื่อหัวข้อ" ของข้อมูล ไม่ใช่ตัวข้อมูล
get_message() มีช่องรับเพียงช่องเดียวนี่คือข้อจำกัดที่เราต้อง สอน ไม่ใช่ซ่อน เพราะมันไม่ส่งเสียงเวลาเกิด
| ค่า | เพดานจริง | เกินแล้วเป็นอย่างไร |
|---|---|---|
| ช่องรับข้อความ | 1 ข้อความ | ข้อความใหม่ทับของเก่าทันที ไม่มีสัญญาณเตือน |
| payload ขาเข้า | 255 ไบต์ | ถูกตัดเงียบ ๆ — JSON ที่ถูกตัดจะ parse ไม่ผ่าน |
| topic ขาเข้า | 127 ไบต์ | ถูกตัดเงียบ |
client_id / username / password |
31 ตัวอักษร | ถูกตัดเงียบ → แพลตฟอร์มหาอุปกรณ์ไม่เจอ → ปฏิเสธการเชื่อมต่อ |
ความล้มเหลวที่แย่ที่สุดสำหรับผู้เรียนคือความล้มเหลวที่เงียบ — จำสี่แถวนี้ไว้ แล้วจะประหยัดเวลาดีบักไปทั้งบทเรียน
mqtt.publish(topic, payload, qos) และ mqtt.subscribe(topic, qos) รับ qos เป็น อาร์กิวเมนต์ตำแหน่ง ไม่ใช่คีย์เวิร์ด
ชุดบทเรียนนี้ใช้ QoS 0 สำหรับ telemetry ทุก 5 วินาที (ค่าถัดไปมาใน 5 วิอยู่แล้ว หายหนึ่งใบไม่เสียหาย) และควรใช้ QoS 1 สำหรับคำสั่ง เพราะคำสั่งที่หายคือคำสั่งที่ผู้ใช้กดแล้วไม่เกิดอะไรขึ้น
MQTT Essentials Part 7 — QoS — HiveMQ · 5 นาที 41 วินาที · อังกฤษ — ดูเพื่อเห็นลำดับแพ็กเก็ตของ QoS 1 และ 2 แบบเต็ม
เลือก QoS จากคำถามเดียว: "ถ้าข้อความนี้หายไปหนึ่งใบ ใครเดือดร้อน"
ตัวเลข 5 วินาทีไม่ได้มาจากความรู้สึก แต่มาจากคำถามว่า ค่าที่เราวัดเปลี่ยนเร็วแค่ไหน อุณหภูมิห้องเปลี่ยนช้ามาก ส่งทุก 5 วินาทีก็ละเอียดเกินพอ ส่วนความสั่นของมอเตอร์เปลี่ยนใน 10 มิลลิวินาที — ค่าแบบนั้นไม่ควรส่งดิบ ๆ ขึ้น MQTT แต่ควรให้บอร์ดสรุปก่อน (ค่าสูงสุด ค่า RMS หรือจำนวนครั้งที่เกินเกณฑ์ในช่วงนั้น) แล้วค่อยส่ง
"ส่งให้ถี่ที่สุดเท่าที่ทำได้" เป็นการออกแบบที่แย่เสมอ — ถามก่อนว่าใครจะใช้ค่านี้ทำอะไร
กติกาการตั้งชื่อ topic ที่ใช้ได้ทั้งชีวิตการทำงาน: เรียงจากกว้างไปแคบ (bento/team03/telemetry ไม่ใช่ telemetry/team03/bento) · ห้ามขึ้นต้นด้วย / เพราะจะได้ช่องว่างเปล่าเป็นชั้นแรก · ห้ามใส่ช่องว่างหรือภาษาไทย · และ อย่าใส่ค่าที่เปลี่ยนบ่อยลงในชื่อ topic (เช่น .../temp/25.4) เพราะผู้รับจะ subscribe ไม่ถูก · บน broker สาธารณะ ใช้รหัสของตัวเองที่ไม่ซ้ำใครแทน team03 เช่นชื่อเล่นต่อด้วยเลขสุ่ม 4 หลัก (nok4821)
สตริงที่เป็นข้อความ เช่น {"status":"ok"} ส่งขึ้นไปได้และเก็บได้ แต่ จะไม่กลายเป็นเส้นกราฟ เพราะกราฟต้องการตัวเลข ถ้าอยากให้เห็นบนกราฟ ให้แปลงเป็นตัวเลขก่อน เช่น {"ok": 1}
ชื่อ topic กับรูปร่าง payload คือ สัญญาระหว่างทีมเรากับทุกคนที่จะใช้ข้อมูลนี้ต่อ เปลี่ยนทีหลังแปลว่าพังทุกฝั่งพร้อมกัน
หน่วยความจำ: บน Eva TLS ต้องการราว 8.8 KB ขณะที่ตอนจับมือมีที่ว่างจริงราว 7 KB ส่วนบน Dev Kit heap ของ MicroPython ถูกย้ายไปอยู่ใน SOCMEM จึงไม่บีบเท่ากัน — แต่นั่นไม่ใช่เหตุผลที่ชุดบทเรียนนี้ใช้ 1883