จุดที่ต่างจากการต่อสายตรงสองเครื่อง: บอร์ดเรา ไม่ได้ส่งถึงบอร์ด B/C/D เราแค่ส่ง "เข้า topic" แล้ว broker เป็นคนกระจายให้ทุกคนที่สมัครไว้ จะมีผู้รับ 1 บอร์ดหรือ 30 บอร์ด โค้ดฝั่งส่งก็เหมือนเดิม

นี่เรียกว่า decoupling (แยกผู้ส่งออกจากผู้รับ) เป็นแนวคิดที่ใช้ซ้ำในงาน IoT จริงทั้งหมด ตั้งแต่เซนเซอร์ในโรงงานไปจนถึงรถยนต์ที่ส่งสถานะขึ้นคลาวด์
Pub Sub Model | MQTT Essentials Part 3 — HiveMQ — อธิบายโมเดล publish/subscribe + บทบาทของ broker สั้น ๆ ตรงกับที่เราทำคาบนี้
ตัวอย่างจริงนอกห้องเรียน: ปลั๊กไฟ เซนเซอร์อุณหภูมิ มือถือ คอมพิวเตอร์ — ทุกอย่าง publish/subscribe ผ่าน broker ตัวเดียว แต่ละ "เรื่อง" คือ topic แยกกัน (เช่น /plug1/voltage, /thermo/temperature)

ที่มา: "MQTT single broker multiple listener" — Ademant, CC BY-SA 4.0, Wikimedia Commons
leaderboard ของเราคือเวอร์ชันจิ๋วของภาพนี้: หลายบอร์ดคุยกันผ่าน broker ตัวเดียว ด้วย topic เดียว ส่วนเครื่องหมาย # / + ในภาพคือ wildcard ของ topic — เก็บไว้ลองตอนน้องอยากรับหลาย topic พร้อมกัน
s12_net.py)ในไฟล์ฝึก practise_codes/s12_net.py↗ มีช่องเว้นไว้ให้น้อง ๆ เติม 4 จุด ตรงกับ 4 ขั้นนี้พอดี อาจารย์เขียนโครงรอบ ๆ ให้แล้ว เราเติมเฉพาะบรรทัดสำคัญ
ก่อนจะคุย MQTT ได้ บอร์ดต้องมีถนนวิ่งก่อน นั่นคือ WiFi wifi.connect คืน True เมื่อต่อสำเร็จ:
import wifi
SSID = "YOUR_WIFI_SSID" # ชื่อ WiFi (2.4GHz) ของห้องเรา
PASSWORD = "YOUR_WIFI_PASSWORD" # รหัส WiFi
print("กำลังต่อ WiFi ...")
if not wifi.connect(SSID, PASSWORD): # คืน True เมื่อต่อสำเร็จ
print("ต่อ WiFi ไม่ได้ ลองเช็ค SSID/รหัสอีกที")
raise SystemExit # ต่อไม่ได้ก็ไม่ต้องไปต่อ
print("ต่อ WiFi แล้ว IP =", wifi.ip()) # โชว์ IP ที่ได้มา
SSID/PASSWORD ตรงนี้เป็นแค่ตัวอย่าง (placeholder) ก่อนรันจริงในห้อง น้อง ๆ ต้องแก้ให้ตรงกับ WiFi ที่อาจารย์เตรียมไว้ และต้องเป็นคลื่น 2.4GHz เท่านั้น
ตรรกะของบล็อกนี้มีแค่ 2 ปลายทาง: ต่อผ่าน → ไปต่อ, ต่อไม่ผ่าน → raise SystemExit หยุดทันที
ต่อสำเร็จ serial console จะพิมพ์ IP ที่ได้มา — นี่คือสถานะ "เน็ตพร้อม" ที่เราอยากเห็นก่อนไปขั้นต่อไป:

ถ้าต่อ WiFi ไม่ผ่าน ก็
raise SystemExitหยุดไปเลย ดีกว่าปล่อยให้โค้ดวิ่งต่อแล้วพังตรง MQTT แบบงง ๆ
ทางลัด: ต่อ WiFi ผ่านเมนู Wi-Fi บนจอ ครั้งเดียว (เซฟรหัส → auto-connect) แล้ว
wifi.connect()ในโค้ดจะผ่านทันที — คาบนี้จะได้เน้นที่ MQTT จริง ๆ
ทบทวน: ชิป PSoC Edge ไม่มีวิทยุในตัว → ใช้ชิป AIROC CYW55513 แยก · MQTT broker = ตัวกลาง publish / subscribe

ขวา: ตัวอย่างโมดูลวิทยุ WiFi + Bluetooth จริง (กล่องโลหะ = ตัว shield คลื่น, ลายทองแดงข้าง ๆ = เสาอากาศบน PCB) — CYW55513 บนบอร์ดเราเป็นชิปประเภทเดียวกัน · ที่มา: "Espressif ESP-WROOM-32 Wi-Fi & Bluetooth Module" — Brian Krent, CC BY-SA 4.0, Wikimedia Commons
client_id ต้องไม่ซ้ำ — broker แยกแต่ละบอร์ดด้วย id ถ้าซ้ำจะเตะกันหลุด.decode() เป็น str หรือแปลงเป็นตัวเลขก่อนใช้Developer I ต่อ WiFi เป็นแล้ว — Developer II เพิ่มความระวัง: id ไม่ซ้ำ + decode payload ก่อนใช้
พอมีถนนแล้ว ก็ไปที่ทำการไปรษณีย์ (broker) พอร์ต MQTT มาตรฐานคือ 1883:
import mqtt
BROKER = "test.mosquitto.org" # broker สาธารณะ ทดสอบได้เลย
TOPIC = "tesaiot/class/leaderboard" # ทั้งห้องใช้ topic เดียวกัน
PLAYER = "board-01" # ชื่อบอร์ดเรา (แต่ละคนตั้งไม่ซ้ำ)
print("กำลังต่อ MQTT broker ...")
if not mqtt.connect(BROKER, 1883, client_id=PLAYER): # คืน True เมื่อสำเร็จ
print("ต่อ broker ไม่ได้")
raise SystemExit
print("ต่อ broker แล้ว")
mqtt.subscribe(TOPIC) # สมัครรับทุกข้อความใน topic นี้
subscribe คือการบอก broker ว่า "มีอะไรเข้า topic นี้ ส่งมาให้ฉันด้วยนะ" ทุกบอร์ดที่ subscribe topic เดียวกัน ก็จะเห็นคะแนนของกันและกัน

หัวใจของคาบ: เล่น Snake/Flappy จบ ได้ my_score มา แล้วห่อเป็นข้อความสั้น ๆ ส่งขึ้นไป
my_score = 1250 # คะแนนที่เพิ่งเล่นจบได้
payload = "%s:%d" % (PLAYER, my_score) # รูปแบบง่าย ๆ "ชื่อ:คะแนน"
mqtt.publish(TOPIC, payload) # ส่งขึ้นกระดาน
print("ส่งคะแนนแล้ว ->", payload)
รูปแบบข้อความ (payload) ที่เราตกลงกันทั้งห้อง:
ทำไมต้องตกลงรูปแบบให้ตรงกัน เพราะฝั่งที่รับต้อง split(":") แยกชื่อกับคะแนนออกมา ถ้ารูปแบบไม่ตรง อ่านไม่ออก นี่คือบทเรียนแรกของ "โปรโตคอลข้อความ" ในงาน IoT
mqtt.get_message() คืน (topic, payload) เป็น bytes เมื่อมีข้อความใหม่ หรือคืน None เมื่อยังไม่มี:
import time
deadline = time.time() + 15 # ฟังคะแนนเพื่อน 15 วินาที
while time.time() < deadline:
msg = mqtt.get_message() # (topic, payload) หรือ None
if msg is not None:
topic, data = msg
print("ได้คะแนนใหม่:", data.decode()) # payload เป็น bytes ต้อง .decode()
time.sleep(0.2) # พักนิดให้ระบบหายใจ
mqtt.disconnect() # จบคาบ ปิดการเชื่อมต่อให้เรียบร้อย

จุดที่นิสิตพลาดบ่อย: ลืม .decode() แล้วจะเห็น b'board-02:980' มี b' นำหน้า เพราะ MQTT ส่งมาเป็น bytes ไม่ใช่ str เราต้องแปลงเองเสมอ
โค้ด publish หนึ่งบรรทัดที่บอร์ดเรา กลายเป็นข้อความที่โผล่บนหลายบอร์ดได้อย่างไร ลองดูจังหวะของมัน:

publish ข้อความ ครั้งเดียว เข้า topicget_message() แล้วได้คะแนนเดียวกันมาแสดงสังเกตว่าบอร์ด A ทำงานแค่ "ส่งเข้า topic" ที่เหลือ broker จัดการเอง น้องไม่ต้องเขียนโค้ดวนส่งไปทีละบอร์ดเลย นี่คือเหตุผลที่ pub/sub สเกลขึ้นง่าย
s12_net.py ในภาพเดียว — connect → publish → รับ4 ขั้นที่แยกกันบนสไลด์ก่อน ประกอบเป็นเส้นทางเดียวของข้อความ: แถวบน = ต่อให้พร้อม (WiFi → broker → publish ครั้งเดียว) · แถวล่าง = วนรับ 15 วิ แล้วปิดการเชื่อมต่อ
สัญกรณ์: ▭ฟ้า = ประมวลผล · ▭เขียว = ส่ง/รับข้อความ · ◇ม่วง = ตัดสินใจ · ▭เทามน = เริ่ม/จบ · เส้นประ = วนกลับ
ยึดตามไฟล์เฉลย: ใน
solution_codes/s12_net.pyการต่อ WiFi ห่อด้วยลูป ลองซ้ำสูงสุด 6 ครั้ง (:25-30, พัก 2 วิ/ครั้ง) — สไลด์ขั้นที่ 1 ย่อเป็นครั้งเดียวเพื่อความกระชับ ผังนี้จึงเขียนwifi.connect() ×≤6ตามของจริง · โปรโตคอลข้อความ"PLAYER:score"→:48; ฝั่งรับdata.decode()แล้วค่อยsplit(":")→:58
ในเกมของเรา (Snake/Flappy) จุดที่ควรส่งคะแนนคือ "ตอนจบรอบ" ไม่ใช่ทุกเฟรม วางสาย publish ไว้ตรงที่เกมจบพอดี:

# --- ในเกม Snake/Flappy: เมื่อจบรอบ (GAME OVER) ---
if game_over:
payload = "%s:%d" % (PLAYER, score) # score = คะแนนรอบนี้
mqtt.publish(TOPIC, payload) # ส่งครั้งเดียวตอนจบ
print("ส่งคะแนนขึ้นกระดานแล้ว:", payload)
เหตุผลที่ส่งครั้งเดียวตอนจบ: คะแนนเปลี่ยนทุกเฟรมก็จริง แต่กระดานสนใจแค่ "คะแนนสุดท้าย" การส่งทุกเฟรมจะถล่ม broker โดยเปล่าประโยชน์ — ส่งเท่าที่จำเป็นคือมารยาทของงานเครือข่าย
ลองคิดเป็นตัวเลข เกมหนึ่งรอบเล่นนาน 10 วินาทีที่ 50 fps ถ้าเรา publish ทุกเฟรม จำนวนข้อความคือ:

ข้อมูลที่มีค่าจริง ๆ คือ "คะแนนสุดท้าย" แค่ตัวเดียว อีก 499 ข้อความเป็นภาระเปล่า ๆ ที่ broker กับ WiFi ต้องแบกไปฟรี ๆ การเลือก "ส่งเท่าที่จำเป็น" คือหัวใจของการออกแบบระบบเครือข่ายที่ดี
ไฟล์เน็ตตัวนี้รันบนบอร์ดเหมือนทุกคาบที่ผ่านมา ขั้นตอนเดิม:
s12_net.py ใน BENTO IDESSID / PASSWORD / PLAYER ให้ตรงของจริง
เราไม่ใช้
exec(open(...))ในการรันนะ การรันเกม/โปรแกรมบนบอร์ดของเราคือกด Program to Device เสมอ วิธีนี้เฟิร์มแวร์จะจัดการโหลดไฟล์ลงบอร์ดให้ถูกต้องเอง
| อาการ | สาเหตุ | วิธีแก้ |
|---|---|---|
| ต่อ WiFi ไม่ผ่าน | SSID/รหัสผิด หรือใช้ 5GHz | ใช้ 2.4GHz, พิมพ์ SSID/รหัสให้เป๊ะ |
| ต่อ broker ไม่ได้ | WiFi ยังไม่พร้อม / พอร์ตผิด | ต่อ WiFi ให้ผ่านก่อน, พอร์ต 1883 |
get_message() คืน None ตลอด |
topic ไม่ตรงกันสองบอร์ด | TOPIC ต้องสะกดเหมือนกันเป๊ะ |
ขึ้น b'...' หน้าข้อความ |
ลืม .decode() |
payload เป็น bytes ต้อง .decode() |
| เกมกระตุก ส่งช้า | publish ทุกเฟรม | ส่งครั้งเดียวตอน GAME OVER |
| คะแนนเพื่อนทับชื่อกัน | PLAYER ซ้ำกัน |
แต่ละบอร์ดตั้งชื่อไม่ซ้ำ |
ตัวที่เจอบ่อยสุดคือลืม .decode() — สังเกตว่า output ฝั่งซ้ายมี b' นำหน้า (ยังเป็น bytes อยู่) พอใส่ .decode() ก็กลายเป็น str สะอาด ๆ ที่ split(":") ต่อได้:

โค้ดส่งคะแนนที่น้องเพิ่งเขียน ไม่ใช่ของเล่นแยกจากงานจริง มันคือรากฐานเดียวกับที่ใช้ทำผลิตภัณฑ์ IoT:
CYW55513 แยก คุยผ่านบัส SDHC — การ "ต่อ WiFi" คือการสั่งฮาร์ดแวร์วิทยุจริง ๆ
bytes ต้อง .decode() เป็น str ก่อน นี่คือเรื่อง type จริงที่เจอทุกครั้งที่อ่านข้อมูลจากเครือข่าย/ไฟล์/เซนเซอร์
"ชื่อ:คะแนน" แล้ว split(":") ฝั่งรับ คือ message protocol แบบย่อ — หลักเดียวกับ JSON/Protobuf ในระบบจริง

ลำโพงอัจฉริยะ/smart-home hub ก็ "ต่อ WiFi แล้ว publish/subscribe ขึ้นคลาวด์" ด้วยสถาปัตยกรรมเดียวกับ leaderboard ของเรา · ที่มา: "Google Home Hub on table" — Y2kcrazyjoker4, CC BY-SA 4.0, Wikimedia Commons
เป้าหมายของคอร์สนี้คือ พอน้องส่งคะแนนเกมขึ้นกระดานเป็น น้องก็ส่งค่าเซนเซอร์ของผลิตภัณฑ์จริงขึ้นคลาวด์เป็น — ด้วยโครงสร้างก้อนเดียวกัน เล่นเป็น แล้วต่อยอดไปสร้างของจริงได้
เกณฑ์ผ่านคาบ 11:
practise_codes/s12_net.py↗ — เดี๋ยวเฉลยพร้อมกันในห้องpublish คะแนนของตัวเองขึ้น topic ได้.decode()ทำเอง 30% (เลือกอย่างน้อย 1):
best = {} (dict ชื่อ→คะแนน) แล้ว print กระดานเรียงจากมากไปน้อยmqtt.publish ไปวางจริงตอน GAME OVER ใน Snake หรือ Flappy ของกลุ่ม (full_games/)try/except ให้เน็ตล่มแล้วเกมไม่ crashส่งงาน: branch feat/leaderboard → commit ไฟล์ทีม → PR (Action py_compile เขียว)
s12_net.py — อ่านให้เข้าใจ ปิดไฟล์ แล้วพิมพ์เองเฉลยตัวนี้มีไว้ให้ เทียบ ไม่ใช่ให้ลอกวางส่ง คะแนนคาบนี้อยู่ที่ใบงานกับการที่น้องอธิบาย publish/subscribe ด้วยคำของตัวเองได้ ไม่ใช่ที่โค้ดตรงเฉลยเป๊ะ วิธีที่ได้ผลจริงคือ อ่านให้เข้าใจ ปิดไฟล์ แล้วพิมพ์ใหม่ด้วยมือ ตอนพิมพ์เองนั่นแหละที่สมองจำ pattern ของงานเน็ตได้ ต่อจากนี้เราจะแกะทีละก้อน ไม่ดูรวดเดียว เพราะทุกบรรทัดมีเหตุผล
ก้อนแรก — ยกค่าตั้งทั้งหมดมาไว้บนสุด:
import wifi
import mqtt
import time
# --- ตั้งค่า: แก้ 4 บรรทัดนี้ให้ตรงกับของจริงก่อนรัน ---
SSID = "YOUR_WIFI_SSID" # ชื่อ WiFi (2.4GHz) ของห้องเรา
PASSWORD = "YOUR_WIFI_PASSWORD" # รหัส WiFi
BROKER = "test.mosquitto.org" # MQTT broker สาธารณะ (ทดสอบได้เลย)
TOPIC = "tesaiot/class/leaderboard" # ห้องเดียวกันทั้งคาบ ทุกคนใช้ topic นี้
PLAYER = "board-01" # ชื่อผู้เล่น/บอร์ดของเรา (แต่ละคนตั้งไม่ซ้ำ)
:15-20) ไม่ปนอยู่กลางโค้ด เวลาย้ายไปรันบอร์ดอื่นน้องแก้แค่หัวไฟล์จุดเดียว ไม่ต้องไล่หาในลูป นี่คือนิสัยเดียวกับที่เราแยก "ค่า" ออกจาก "โครง" มาตลอดคอร์สSSID / PASSWORD เป็นแค่ placeholder และต้องเป็นคลื่น 2.4GHz เท่านั้น ตรงกับตารางกับดักที่เราดูไปแล้ว (ต่อ 5GHz จะไม่ติด)BROKER กับ TOPIC (:17-18) คือ "ที่ทำการไปรษณีย์" กับ "ชื่อกล่องจดหมาย" จากสไลด์ภาพรวม ทั้งห้องต้องใช้ TOPIC สะกดเหมือนกันเป๊ะ ไม่งั้น get_message() จะคืน None ตลอดPLAYER (:20) ทำหน้าที่สองอย่างพร้อมกัน: เป็นชื่อในกระดาน และเดี๋ยวถูกส่งเป็น client_id ให้ broker แยกบอร์ด ตรงกับกฎ "id ต้องไม่ซ้ำ" ในสไลด์ฮาร์ดแวร์ ซ้ำเมื่อไรบอร์ดเตะกันหลุดค่าตั้งที่ต้องแก้บ่อย ให้มันอยู่ที่เดียวบนหัวไฟล์เสมอ วันหลังน้องจะขอบคุณตัวเองที่ไม่ต้องไล่หามันกลางโค้ด
บนสไลด์ขั้นที่ 1 อาจารย์ย่อการต่อ WiFi ให้เหลือครั้งเดียวเพื่อความกระชับ แต่ในเฉลยจริงมันห่อด้วยลูปลองซ้ำ เพราะครั้งแรกวิทยุมักยังไม่ติด:
# --- 1) ต่อ WiFi ---
print("กำลังต่อ WiFi ...")
ok = False
for i in range(6): # ลองสูงสุด 6 ครั้ง (ครั้งแรกมักยังไม่ติด)
if wifi.connect(SSID, PASSWORD): # คืน True เมื่อต่อสำเร็จ
ok = True
break
print(" ยังไม่ติด ลองใหม่ %d/6 ..." % (i + 1))
time.sleep(2) # พัก 2 วิ ให้ WCM ตั้งตัว
if not ok:
print("ต่อ WiFi ไม่ได้ ลองเช็ค SSID/รหัสอีกที")
raise SystemExit
print("ต่อ WiFi แล้ว IP =", wifi.ip()) # โชว์ IP ที่ได้มา
ok เริ่มที่ False (:24) แล้วพลิกเป็น True กับ break เฉพาะตอนต่อสำเร็จ (:26-28) นี่คือ pattern "flag + ลูป" มาตรฐาน ต่อได้เมื่อไรออกจากลูปทันที ไม่ลองต่อทิ้งเปล่าrange(6) ไม่ใช่ลองครั้งเดียว? เพราะการปลุกชิปวิทยุ (WCM) ใช้เวลาตั้งตัว time.sleep(2) (:30) คั่นให้มันหายใจ ลองรวดเดียวมักพลาดทั้งที่ SSID ถูก นี่คือความจริงของฮาร์ดแวร์ที่โค้ดบนกระดาษไม่เห็นif not ok: จะ raise SystemExit (:31-33) หยุดตรงนี้เลย ดีกว่าปล่อยให้ไหลไปพังตรง MQTT แบบงง ตรงกับคาถา "ต่อ WiFi ไม่ผ่านก็ไม่ต้องไปต่อ"wifi.connect() ×≤6 — โค้ดก้อนนี้คือกล่องนั้นแบบเต็มเขียนโค้ดคุยกับฮาร์ดแวร์ ให้เผื่อ "ครั้งแรกมันยังไม่พร้อม" ไว้เสมอ ลองซ้ำอย่างมีขีดจำกัด แล้วถ้าหมดโควตาค่อยยอมแพ้อย่างมีศักดิ์ศรี ด้วย
raise SystemExit
พอมีถนน (WiFi) แล้ว ก็ต่อ broker แล้วส่งคะแนน สองขั้นนี้ต่อเนื่องกันในเฉลย:
# --- 2) ต่อ MQTT broker ---
print("กำลังต่อ MQTT broker ...")
if not mqtt.connect(BROKER, 1883, client_id=PLAYER): # พอร์ต MQTT ปกติคือ 1883
print("ต่อ broker ไม่ได้")
raise SystemExit
print("ต่อ broker แล้ว")
# สมัครรับข้อความใน topic เดียวกัน เพื่อเห็นคะแนนของทุกบอร์ด
mqtt.subscribe(TOPIC)
# --- 3) ส่งคะแนนของเราขึ้นกระดาน ---
my_score = 1250 # สมมุติว่าเพิ่งเล่นจบได้เท่านี้
payload = "%s:%d" % (PLAYER, my_score) # รูปแบบง่าย ๆ "ชื่อ:คะแนน"
mqtt.publish(TOPIC, payload)
print("ส่งคะแนนแล้ว ->", payload)
mqtt.connect(BROKER, 1883, client_id=PLAYER) (:38) ใช้ guard แบบเดียวกับ WiFi เป๊ะ ต่อไม่ได้ก็ raise SystemExit พอร์ต 1883 คือพอร์ต MQTT มาตรฐาน และ client_id=PLAYER คือเหตุผลที่ชื่อบอร์ดต้องไม่ซ้ำmqtt.subscribe(TOPIC) (:44) วางไว้ ก่อน publish จงใจ สมัครรับก่อนแล้วค่อยส่ง จะได้ไม่พลาดคะแนนของเพื่อนที่เข้ามาระหว่างนั้น นี่คือ pub/sub decoupling จากสไลด์ "ผู้ส่งไม่ต้องรู้จักผู้รับ": เราคุยกับ topic ไม่ได้คุยกับบอร์ดใครโดยตรงpayload = "%s:%d" % (PLAYER, my_score) (:48) คือหัวใจ เราตกลง โปรโตคอลข้อความ ว่า "ชื่อ:คะแนน" ทั้งห้อง เพราะฝั่งรับต้อง split(":") แยกคืน ถ้ารูปแบบไม่ตรงกัน อ่านไม่ออกทันทีmy_score = 1250 เป็นแค่ค่าจำลอง (:47) ในเกมจริงตรงนี้คือคะแนนตอน GAME OVER ตามสไลด์ "เรียกตอน GAME OVER" ส่งครั้งเดียวพอก่อนสองเครื่องจะคุยกันรู้เรื่อง ต้องตกลง "รูปแบบข้อความ" ให้ตรงกันก่อนเสมอ
"ชื่อ:คะแนน"วันนี้คือ JSON/Protobuf เวอร์ชันจิ๋วในระบบจริงพรุ่งนี้
ปิดท้ายด้วยลูปฟังคะแนนเพื่อน แล้วเก็บกวาดปิดการเชื่อมต่อให้เรียบร้อย:
# --- 4) วนรับคะแนนของเพื่อน 15 วินาที ---
deadline = time.time() + 15
while time.time() < deadline:
msg = mqtt.get_message() # คืน (topic, payload) เป็น bytes หรือ None
if msg is not None:
topic, data = msg
print("ได้คะแนนใหม่:", data.decode()) # payload เป็น bytes ต้อง .decode()
time.sleep(0.2) # พักนิดให้ระบบหายใจ
mqtt.disconnect()
print("จบคาบ ปิดการเชื่อมต่อเรียบร้อย")
deadline = time.time() + 15 (:53) แล้ววน while time.time() < deadline คือการ "จับเวลาแทนการนับรอบ" ฟังไป 15 วิแล้วออกเอง ไม่ค้างตลอดกาลif msg is not None: (:56) สำคัญมาก เพราะ get_message() คืน None เมื่อยังไม่มีข้อความ เช็ก None ก่อนค่อยแกะ ไม่งั้น unpack topic, data = msg จะพังdata.decode() (:58) คือกับดักที่นิสิตพลาดบ่อยสุด payload มาเป็น bytes เห็น b'...' นำหน้า ต้อง .decode() เป็น str ก่อน split(":") ตรงกับสไลด์ before/aftermqtt.disconnect() (:61) ปิดการเชื่อมต่อตอนจบ คืนบอร์ดสู่สถานะที่รู้แน่ (known state) เป็นนิสัยเก็บกวาดจาก Developer I: ออกจากงานยังไง ทิ้งเครื่องไว้เรียบร้อยแบบนั้นสี่ก้อนนี้ต่อกันเป็นเส้นเดียว แต่ละก้อนพึ่งก้อนก่อนหน้า:
| ก้อน | บรรทัด | แนวคิดใหม่ที่เพิ่ม | ประกอบต่อจากก้อนก่อน |
|---|---|---|---|
| 1 WiFi | :22-34 |
ต่อวิทยุจริง + ลองซ้ำ ×6 + guard | for/if/flag ที่เคยเขียนมาแล้ว |
| 2 MQTT | :36-44 |
client_id + พอร์ต 1883 + subscribe |
guard แบบเดียวกับ WiFi ก้อน 1 |
| 3 publish | :46-50 |
โปรโตคอลข้อความ "%s:%d" |
ต้องมี broker จากก้อน 2 ก่อนถึงส่งได้ |
| 4 receive | :52-62 |
ลูปจับเวลา + None-check + .decode() + cleanup |
ฟังจาก TOPIC เดียวกับที่ subscribe ก้อน 2 |
อ่านตารางนี้จากบนลงล่างจะเห็นว่า งานเน็ตไม่ใช่เวทมนตร์ก้อนเดียว มันคือสี่ก้อนเล็กที่ต่อกัน แต่ละก้อนเปิดทางให้ก้อนถัดไป ถ้าก้อน 1 ไม่ผ่าน ก้อน 2-4 ก็ไม่มีความหมาย จับลำดับนี้ให้ได้ก่อนพิมพ์เอง
หยุดคิดสักครู่ก่อนปิดคาบ ไม่ใช่เรื่องไวยากรณ์ แต่เรื่อง "มองให้ทะลุ" ว่า mqtt.publish บรรทัดเดียวที่น้องเพิ่งเขียน จริง ๆ แล้วมันคือชิ้นส่วนของอะไร
ที่มา — บรรทัดนี้ยืนอยู่บนอะไร ย้อนไปสไลด์ "ทำไม pub/sub ถึงทรงพลัง: ผู้ส่งไม่ต้องรู้จักผู้รับ" กับสไลด์ภาพรวม ที่คะแนนเดินทางผ่าน broker แทนที่จะลากสายตรงหากันทีละบอร์ด ปัญหาที่ pub/sub เกิดมาแก้แต่แรกคือ "จะให้ผู้ส่งหนึ่งคน คุยกับผู้รับกี่คนก็ได้ โดยไม่ต้องรู้จักกัน" ได้ยังไง คำตอบคือส่ง "เข้า topic" ไม่ใช่ส่ง "ถึงใคร" และ Developer I ที่น้องต่อ WiFi เป็นแล้ว คือถนนที่ปูรอไว้ให้ก้อนนี้พอดี
ที่ไป — บรรทัดนี้จะโตเป็นอะไร ทวนสไลด์ "leaderboard นี้คือวิศวกรรม IoT จริง" ที่อาจารย์บอกว่า พอส่งคะแนนขึ้นกระดานเป็น น้องก็ส่งค่าเซนเซอร์ขึ้นคลาวด์เป็น ด้วยโครงก้อนเดียวกัน เปลี่ยนแค่ payload กับ TOPIC และคาบหน้าเป็น Capstone ออกแบบเกมเอง จุดที่ leaderboard จะกลับมาคือ "ฝังตอน GAME OVER ในเกมของกลุ่ม"
ลองตอบคำถามพวกนี้ในใจ นี่แหละคือการเชื่อมจุดด้วยตัวเอง:
TOPIC แทนที่จะส่งตรงไปหาบอร์ด B/C/D ทีละตัว? (ใบ้: ถ้าห้องมี 30 บอร์ด โค้ดฝั่งส่งของเราต้องเปลี่ยนไหม)"board-01:1250" ขึ้น topic ได้ พรุ่งนี้เราส่ง "livingroom:27.5" (อุณหภูมิห้อง) ขึ้น topic เดียวกันได้ไหม โครงโค้ดต้องเปลี่ยนตรงไหนบ้าง? (ใบ้: แค่ payload กับ TOPIC)data.decode() ตอนรับ MQTT มันคือเรื่องเดียวกับตอนอ่านค่าดิบจากเซนเซอร์แล้วต้องแปลง type ก่อนใช้? bytes → str วันนี้ กับ raw → ค่าที่อ่านได้ คือหลักเดียวกันถ้าสามคำถามข้างบนน้องตอบได้ว่า "อ๋อ มันคือเรื่องเดียวกัน" นั่นคือการหยั่งรู้ที่อาจารย์อยากให้เกิด ก้อนโค้ดที่ดูเล็กวันนี้ ไม่ได้เล็ก มันคือเมล็ดของระบบ IoT ทั้งระบบ เราแค่ยังไม่ได้รดน้ำ
เทคนิควันนี้ไม่ใช่แค่แบบฝึกในห้อง มันคือสิ่งที่ระบบจริงข้างนอกใช้กันทุกวัน:
home/livingroom/light ตรงกับหลัก "ทั้งห้องตกลง TOPIC ให้ตรงกัน" — ต่างแค่ตั้งชื่อ topic เป็นชั้น ๆclient_id ไม่ซ้ำ ศูนย์กลาง subscribe ดูทั้งกอง ตรงกับกฎ "id ต้องไม่ซ้ำ" — ซ้ำเมื่อไรเตะกันหลุด งานจริงคือเรื่องคอขาดบาดตายสี่อย่างข้างบนไม่มีอันไหนเป็นของสมมติเลย ทุกอันวางอยู่บนสามท่าเดิมของเราวันนี้ pub/sub · โปรโตคอลข้อความ · ส่งเท่าที่จำเป็น เปลี่ยนแค่ payload กับ topic เท่านั้นเอง
ลองเอาโจทย์พวกนี้ไปคิดต่อ ไม่มีคำตอบเดียวตายตัว ทุกข้อโยงกลับไปหาของจริงได้:
"ชื่อ:คะแนน" จะพอไหม ออกแบบ payload ใหม่ยังไงให้ฝั่งรับแยกคืนง่าย (ลองมองไปทาง JSON)try/except + คิวเก็บคะแนน ตรงกับงาน 30%)best = {} (ชื่อ→คะแนนสูงสุด) แล้วเรียงยังไงไม่ต้องคำนวณใหม่ทุกข้อความTOPIC ยังไงให้คะแนนแต่ละกลุ่ม ไม่ปนกัน (ใบ้: topic เป็นชั้น เช่น tesaiot/class/<ชื่อเกม>/leaderboard) — นี่คือสิ่งที่เกม Capstone ต้องตัดสินใจเลือกมาสักข้อ แล้วเขียนลงใบงานว่า "ถ้าเป็นเรา จะออกแบบยังไง" ไม่ต้องมีคำตอบถูกวันนี้ ขอแค่คิดต่อจากโค้ดที่พิมพ์เอง ตรงนั้นแหละคือจุดที่น้องเริ่มเป็นวิศวกรเครือข่าย ไม่ใช่แค่คนพิมพ์ตามเฉลย
fit-css
Marp deck — Fundamental of Embedded Systems Developer II (MicroPython-only, 13 คาบ) คาบ 11 · WiFi + MQTT: high-score board Render: npx @marp-team/marp-cli session-11.md -o session-11.html GUI-first. ภาพจริงบนบอร์ดฝังผ่าน img/. โค้ดอ้างอิง: practise_codes/s12_net.py (ฉบับฝึก)