แนวคิด network/IoT ไม่ได้ลึกลับเลย บอร์ดคือ node, broker คือ ที่ทำการไปรษณีย์กลาง, ส่วนคะแนนที่เราส่งก็เหมือน โปสการ์ดใบหนึ่ง
หัวใจของ MQTT คือ topic: ถ้าใช้ชื่อ topic เดียวกัน ก็เหมือนอยู่ห้องเดียวกัน ได้ยินกันหมด
จุดที่ทำให้ leaderboard ของทั้งห้องทำงานได้ คือ บอร์ดเราส่ง 1 ครั้ง แต่ broker กระจายให้ทุกคนที่ subscribe topic เดียวกัน เราไม่ต้องรู้เลยว่ามีใครฟังอยู่บ้าง

นี่คือเหตุผลที่ MQTT เหมาะกับ IoT จริง ๆ ลองนึกถึงเซ็นเซอร์อุณหภูมิ 100 ตัวในโรงงาน ส่งขึ้น topic เดียว แดชบอร์ดตัวเดียว subscribe ก็เห็นครบ — โครงเดียวกับ leaderboard ของเราเป๊ะ

บอร์ด A publish คะแนนขึ้น broker ครั้งเดียว จากนั้น broker ก็ส่งต่อให้บอร์ด B และ C ที่ subscribe topic นั้นไว้ พร้อมกัน — รวมถึงส่งกลับให้บอร์ด A เองด้วย (เพราะ A ก็ subscribe อยู่)
สังเกตว่า A ไม่ได้ส่งตรงไปหา B หรือ C เลย มันส่งให้ "ห้อง" (topic) เฉย ๆ ใครอยู่ในห้องก็ได้ยินเอง — แยกหน้าที่ "ส่ง" ออกจาก "ใครรับ" ชัดเจน
เชื่อมโยงรากฐาน — embedded + Python ที่อยู่ใต้เกมนี้: (1) ฮาร์ดแวร์ — บอร์ดสื่อสารผ่านชิปวิทยุแยก (AIROC) ผ่านบัส SDIO/UART ทุกอุปกรณ์ IoT จริงก็คือ MCU + radio แบบนี้ (2) Python —
get_message()คืนNoneเมื่อยังไม่มีของ นี่คือ non-blocking polling เกมจึงวนเช็กได้โดยไม่ค้าง และ payload ที่รับมาเป็นbytesต้อง.decode()เป็นstrก่อน (เรื่อง type เดิมจากคาบต้น ๆ) — เมื่อจับสองอย่างนี้ได้ คุณก็ต่อเซ็นเซอร์จริงขึ้นคลาวด์ได้ด้วยหลักการเดียวกัน
WiFi มี 3 ฟังก์ชันหลักที่เราใช้วันนี้ จำง่าย ๆ คือ ต่อ / เช็ก / ถาม IP

import wifi
import time
# ต่อ WiFi (ห้องเราเป็น 2.4GHz) — ลองสูงสุด 6 ครั้ง (ครั้งแรกมักยังไม่ติด)
ok = False
for i in range(6):
if wifi.connect("YOUR_WIFI_SSID", "YOUR_WIFI_PASSWORD"): # คืน True เมื่อสำเร็จ
ok = True
break
print(" ยังไม่ติด ลองใหม่ %d/6 ..." % (i + 1))
time.sleep(2) # พัก 2 วิ ให้ WCM ตั้งตัว
if not ok: # ลองครบ 6 ครั้งแล้วยังไม่ติด → อย่าไปต่อ
print("ต่อ WiFi ไม่ได้ ลองเช็ค SSID/รหัสอีกที")
raise SystemExit
print("IP ของบอร์ดเรา:", wifi.ip()) # เช่น 192.168.1.42
ทำไมต้องเป็น loop ลองใหม่ ไม่ใช่ยิงครั้งเดียว? เพราะการจับ AP + ขอ IP ใช้เวลาไม่แน่นอน ครั้งแรกจึงมัก "ยังไม่ติด" — วนลองพร้อมหน่วง time.sleep(2) คือแพตเทิร์น retry มาตรฐานของงานเครือข่ายจริง (ตรงกับ s13_net.py:25–33)
ข้อควรระวัง:
SSID / PASSWORD วันนี้เป็น ค่า placeholder ต้องแก้ให้ตรงของห้องเราก่อนทางลัดที่แนะนำ: ต่อ WiFi ผ่านเมนู Wi-Fi บนจอ ครั้งเดียว (เซฟรหัสไว้ → บอร์ด auto-connect ทุกครั้งที่เปิด) แล้วโค้ดนี้จะโฟกัสที่ MQTT ล้วน ๆ — บรรทัด
wifi.connect()จะผ่านทันทีเพราะต่ออยู่แล้ว
ชิป PSoC Edge ไม่มีวิทยุในตัว — มันจับคู่กับชิปวิทยุแยกชื่อ AIROC CYW55513 (Wi-Fi 6/6E + Bluetooth) คุยกันผ่าน SDIO + UART

"ชิปวิทยุแยก" ในรูปขวาคือการ์ดวิทยุแบบ M.2 (Wi-Fi 6) ตัวจริง — สังเกตขั้วต่อเสาอากาศ MAIN/AUX และโลหะคลุมชิป RF · ที่มา: "Intel Wi-Fi 6 AX200NGW card" — Siarhei Besarab, CC BY-SA 4.0, Wikimedia Commons
wifi.connect() = ส่ง SSID/รหัสให้ชิปวิทยุไปต่อ AP (router/แอคเซสพอยต์ของห้อง) แล้วได้ IP address กลับมา
mqtt.publish("score", "42")= หย่อนจดหมายติดป้าย "score" ที่ไปรษณีย์ — ทุกบอร์ดที่สมัครรับป้ายนี้ได้อ่านหมด (payload ต้องเป็นstrหรือbytes— ถ้าเป็นตัวเลขให้ครอบด้วยstr(...)ก่อน)
What is MQTT | MQTT Essentials Part 1 — HiveMQ — publish/subscribe, topic, broker อธิบายภาพรวมตรงกับที่เราต่อในคาบนี้
wifi.connect() ปลายทางไม่ใช่ของลึกลับ มันคือกล่อง access point / router แบบในรูปนี้ ที่กระจายคลื่น 2.4GHz อยู่ในห้องเรา
ในผลิตภัณฑ์จริง อุปกรณ์ IoT ทุกตัวก็เริ่มจากก้าวนี้เป๊ะ — จับ AP ให้ได้ IP ก่อน แล้วค่อยคุย protocol ที่อยู่ชั้นบน (MQTT/HTTP) ทีหลัง
ที่มา: "ELECOM WRC-300FEBK WPS WiFi router" — Syced, CC0, Wikimedia Commons
ต่อ WiFi ติดแล้ว ค่อยต่อ broker ลำดับนี้ห้ามสลับ เพราะ MQTT วิ่งบนเน็ตอีกที
import mqtt
BROKER = "test.mosquitto.org" # broker สาธารณะ ทดสอบได้เลย (placeholder)
TOPIC = "tesaiot/class/leaderboard" # ทุกบอร์ดในห้องใช้ topic นี้ร่วมกัน
PLAYER = "board-01" # ชื่อบอร์ดเรา แต่ละคนตั้งไม่ให้ซ้ำ
# พอร์ต MQTT ปกติคือ 1883 — คืน True เมื่อต่อสำเร็จ
if not mqtt.connect(BROKER, 1883, client_id=PLAYER):
print("ต่อ broker ไม่ได้")
raise SystemExit
mqtt.subscribe(TOPIC) # สมัครรับ = "ขอฟังห้องนี้ด้วย"
print("ต่อ broker แล้ว")
subscribe คือการบอก broker ว่า "เวลามีใครส่งเข้า topic นี้ ส่งมาให้ผมด้วย"
ส่งคะแนนด้วย publish, รับคะแนนคนอื่นด้วย get_message ซึ่ง ไม่บล็อก (ไม่มีของก็คืน None)

import time
my_score = 1250 # สมมุติเพิ่งเล่นจบได้เท่านี้
payload = "%s:%d" % (PLAYER, my_score) # รูปแบบง่าย ๆ "ชื่อ:คะแนน"
mqtt.publish(TOPIC, payload) # ส่งคะแนนขึ้นกระดาน
print("ส่งคะแนนแล้ว ->", payload)
deadline = time.time() + 15 # วนรับคะแนนเพื่อน 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() # ปิดการเชื่อมต่อเมื่อจบ
get_message() คืน None เมื่อยังไม่มีของ — เกมจึงไม่ค้าง วนเช็กไปเรื่อย ๆ ได้
สะพานทฤษฎี → โค้ด (3 จุดสำคัญ):
"ชื่อ:คะแนน" ด้วย "%s:%d" % (PLAYER, my_score) (s13_net.py:48) ฝั่งรับ .decode() แล้ว split(":") แยกกลับ (:57–58) → นี่คือ "protocol เล็ก ๆ" ที่ทั้งห้องตกลงกัน ถ้าอยากส่งหลายฟิลด์ให้เป็นระเบียบกว่านี้ ใช้ json.dumps({"name":PLAYER,"score":my_score}) แล้วฝั่งรับ json.loads(...) ได้เลยmqtt.publish (s13_net.py:49) — MQTT มี 3 ระดับความมั่นใจว่าข้อความถึงปลายทาง: QoS 0 ("ส่งแล้วแล้วกัน" เร็วสุด — ค่าปริยายของ leaderboard นี้), QoS 1 ("อย่างน้อย 1 ครั้ง" อาจซ้ำ), QoS 2 ("ครั้งเดียวเป๊ะ" ช้าสุด) สำหรับคะแนนเกม QoS 0 พอ เพราะเดี๋ยวก็ส่งค่าใหม่ทับอยู่ดีTrue/False ถ้า not ok ก็ raise SystemExit ทันที (WiFi :31–33, broker :39–40) ไม่ปล่อยให้พังเงียบ ๆ ตอนหลังลำดับนี้คือ "สัญญา" ของเน็ต: ถ้า step ก่อนหน้าไม่สำเร็จ step ถัดไปจะพังเงียบ ๆ จึงต้องเช็กผลทุกครั้ง
s13_net.py — เฟส 1: ต่อเน็ต + brokerผังนี้คือครึ่งแรกของไฟล์ทั้งไฟล์: จาก START ผ่าน retry loop ต่อ WiFi → เช็ก → ต่อ broker → subscribe เส้นทางล้มเหลวทุกทางลง raise SystemExit
เราตกลงกัน รูปแบบเดียวทั้งห้อง เพื่อให้ทุกบอร์ดอ่านของกันออก: "ชื่อ:คะแนน"
ตอนรับมาเป็น bytes เราจึงต้อง .decode() ก่อนเสมอ แล้วค่อย split(":") ถ้าจะแยกชื่อกับคะแนน
practise_codes/s13_net.py↗wifi.connect · (2) wifi.ip()mqtt.connect · (4) mqtt.publishวิธีรัน: เปิดไฟล์ใน BENTO IDE แล้วกดปุ่ม Program to Device (ปุ่มสีเขียวมุมซ้ายบน — ดูรูป) บอร์ดจะรันให้ทันที (ดูผลที่คอนโซลด้านล่าง)

ปุ่ม Program to Device อยู่บนแถบเครื่องมือ BENTO IDE (มุมซ้ายบน) · กดแล้ว IDE จะเซฟเป็น main.py แล้วสั่งบอร์ดรัน — ผลลัพธ์ขึ้นที่ Terminal ด้านล่าง
เกมเต็มที่ดึงคะแนนจริงมาต่อ leaderboard อยู่ใน
full_games/ไว้ดูเป็นปลายทางได้
leaderboard ไม่ใช่วิดีโอสด เราอยากเห็นคะแนน ล่าสุด ไม่ใช่คะแนนทุกเสี้ยววินาที — ดังนั้นอย่า publish ในทุกเฟรมของเกม

ถ้าเกมรันที่ เฟรมต่อวินาที แล้ว publish ทุกเฟรม ภาระต่อ 1 นาทีคือ
เทียบกับส่ง เมื่อคะแนนเปลี่ยน ซึ่งในเกมจริงเกิดไม่กี่ครั้งต่อนาที ภาระต่างกันเป็นหลักร้อยเท่า:
เก็บคะแนนล่าสุดที่ส่งไปไว้ในตัวแปร แล้ว
publishก็ต่อเมื่อscore != last_sentเท่านั้น — ลดภาระ broker, ลดเน็ตหน่วง, และคะแนนบนกระดานก็ยังสด นี่คือเทคนิคเดียวกับที่ใช้คุมอัตราส่งของเซ็นเซอร์จริงในงาน IoT
หมายเหตุ: ไฟล์เดโม
s13_net.pyส่งคะแนนค่าคงที่ครั้งเดียว (my_score = 1250แล้วpublishที่:47–50) เพื่อให้ทดสอบง่าย — การ์ดscore != last_sentด้านบนคือแพตเทิร์นที่ควรใส่เมื่อนำไปต่อในลูปเกมจริง ที่คะแนนเปลี่ยนได้
| อาการ | สาเหตุ | วิธีแก้ |
|---|---|---|
wifi.connect คืน False |
SSID/รหัสผิด หรือเป็น 5GHz | ใช้ network 2.4GHz + เช็กรหัสอีกครั้ง |
wifi.ip() ได้ค่าแปลก ๆ |
เรียกก่อนต่อสำเร็จ | เช็ก is_connected() ให้ True ก่อน |
mqtt.connect ติดบ้างไม่ติดบ้าง |
client_id ซ้ำกับเพื่อน |
ตั้ง PLAYER ให้ไม่ซ้ำทั้งห้อง |
get_message() คืน None ตลอด |
topic ไม่ตรงกัน | TOPIC ต้องเป๊ะ ตัวเดียวกันทุกบอร์ด |
data แสดงเป็น b'...' |
ลืม .decode() |
payload เป็น bytes ต้อง decode ก่อน |
| ส่งคะแนนทุกเฟรม เน็ตหน่วง | publish ถี่เกิน | ส่งเมื่อคะแนน "เปลี่ยน" พอ ไม่ใช่ทุกเฟรม |
เกณฑ์ผ่านคาบ 13:
wifi.is_connected() เป็น True และเห็น IP ของบอร์ดเราmqtt.publish คะแนนขึ้น topic ของห้องส่วนทำเอง 30% (ต่อยอดเลือก 1 อย่าง):
dict แล้ว print เป็นกระดานเรียงจากมากไปน้อย"ชื่อ:คะแนน:ด่าน" แล้วแยกด้วย split(":")PLAYER คนละชื่อแต่ TOPIC เดียวกันคาบหน้า (คาบ 14) เราจะรวมทุกอย่างที่ฝึกมาทั้งคอร์สเป็นโปรเจกต์ปิดท้ายของตัวเอง เน็ตที่ทำวันนี้จะเป็นของพิเศษชิ้นสำคัญที่ทำให้งานเราโดดเด่น
s13_net.py — อ่านให้เข้าใจ แล้วปิดไฟล์พิมพ์เองเฉลยนี้ไม่ได้มีไว้ลอกวางส่ง คะแนนคาบนี้อยู่ที่ใบงานกับการอธิบายด้วยคำพูดของน้องเอง วิธีใช้ให้ได้ผลจริงคือ อ่านให้เข้าใจ ปิดไฟล์ แล้วพิมพ์ใหม่ด้วยมือ ตอนพิมพ์เองนี่แหละสมองจะจำ 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/TOPIC/PLAYER ให้จบในหน้าจอเดียว ไม่ต้องไล่หาทีละจุด — ตรงกับสไลด์ practise ที่บอกว่ามี 4 จุดต้องเติม ถ้าฝังค่าลึกในโค้ด เพื่อนที่หยิบไฟล์ไปรันต่อจะแก้ผิดจุดทันทีTOPIC (:18) คือ "ห้อง" จากสไลด์ node/broker — ทั้งห้องต้องพิมพ์เป๊ะตัวเดียวกัน พิมพ์ต่างแม้แต่ตัวเดียว จะเข้าคนละห้อง ตรงกับกับดัก "topic ไม่ตรง get_message คืน None ตลอด"PLAYER (:20) ต้องไม่ซ้ำทั้งห้อง เพราะมันไปเป็น client_id ของ broker — จำกับดัก "client_id ซ้ำ ต่อบ้างไม่ติดบ้าง" ได้ไหม สองบอร์ดใช้ชื่อเดียว broker จะเตะตัวเก่าออกimport wifi / mqtt / time (:10-12) สามโมดูลพอดีกับงานวันนี้: wifi ต่อเน็ต, mqtt คุย broker, time คุมจังหวะทั้ง retry และ pollค่าที่ต้องแก้ก่อนรัน ให้อยู่ที่เดียว มองเห็นหมดในตาเดียว นี่เป็นนิสัยเล็ก ๆ ที่ช่วยทั้งตัวเราและคนที่หยิบโค้ดไปใช้ต่อ
# --- 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 ที่ได้มา
wifi.connect ครั้งเดียว? เพราะการจับ AP แล้วขอ IP ใช้เวลาไม่แน่นอน ครั้งแรกมักยังไม่ติด นี่คือ retry loop มาตรฐานที่เราคุยกันในสไลด์ ก้าวที่ 1 — ยิงครั้งเดียวแล้วเชื่อว่าติดเลยคือสาเหตุที่โค้ดเน็ตล้มบ่อยที่สุด (:25-30)ok = False แล้วตั้งเป็น True ตอนสำเร็จ (:24,27) คือธง (flag) ที่บอกว่า "ออกจากลูปเพราะติดจริง หรือเพราะลองครบ 6 ครั้งแล้ว" ถ้าไม่มีธงนี้ เราแยกสองกรณีนี้ไม่ออกif not ok: raise SystemExit (:31-33) คือด่านกั้น — ต่อเน็ตไม่ติดก็อย่าไปต่อ MQTT ให้เสียเวลา ตรงกับหลัก "เช็กผลก่อนไปต่อ" จากสไลด์วงจรชีวิตการเชื่อมต่อ ("ถ้า step ก่อนไม่สำเร็จ step ถัดไปจะพังเงียบ ๆ")wifi.ip() เรียก หลัง ต่อสำเร็จเท่านั้น (:34) — จำกับดัก "เรียก ip() ก่อนต่อสำเร็จ ได้ค่าแปลก ๆ" ได้ไหม ที่นี่เราปลอดภัยเพราะผ่านด่าน not ok มาแล้วการต่อเน็ตที่ดีคือการ "ลองใหม่อย่างมีขอบเขต" ไม่ใช่ลองรอบเดียวแล้วยอมแพ้ และไม่ใช่วนไม่รู้จบ — 6 ครั้งพร้อมหน่วง 2 วิ คือจุดพอดีที่ตั้งใจเลือกไว้
# --- 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 มา หลัง WiFi เสมอ (:38) — ลำดับนี้ห้ามสลับ MQTT วิ่งบนเน็ตอีกชั้น เหมือนที่เราวาดในสไลด์วงจรชีวิต (wifi.connect → mqtt.connect → subscribe) ต่อ broker ก่อนได้ IP คือต่อไม่ติดแน่นอนclient_id=PLAYER เป็น keyword argument (:38) ที่ผูกชื่อบอร์ดเข้ากับ session ของ broker นี่คือเหตุผลที่ PLAYER ต้องไม่ซ้ำ — โยงกลับไปกับดัก client_id ซ้ำโดยตรงsubscribe มา ก่อน publish (:44 ก่อน :49)? เพราะเราอยากได้ยินของทุกคน รวมทั้งของตัวเอง — จำสไลด์ "publish หนึ่งครั้ง วิ่งถึงทุกคน" ที่บอกว่า broker ส่งกลับให้ A เองด้วยไหม ถ้า subscribe ทีหลัง อาจพลาดข้อความที่ส่งมาช่วงต้นpayload = "%s:%d" % (PLAYER, my_score) (:48) คือ application protocol เล็ก ๆ ที่ทั้งห้องตกลงกัน รูปแบบ "ชื่อ:คะแนน" จากสไลด์ leaderboard — ฝั่งส่งประกอบ string ฝั่งรับ split(":") แยกกลับ ตกลงรูปแบบให้ตรงกันคือหัวใจของการคุยข้ามเครื่อง
subscribeก่อนpublishไม่ใช่เรื่องบังเอิญ เราตั้งใจ "เข้าห้องไปนั่งฟัง" ให้เรียบร้อยก่อนค่อยพูด จะได้ไม่พลาดเสียงใคร รวมถึงเสียงของเราเอง
# --- 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("จบคาบ ปิดการเชื่อมต่อเรียบร้อย")
get_message() เป็นแบบ ไม่บล็อก — ไม่มีของก็คืน None (:55-56) เราจึงวน while เช็กเรื่อย ๆ ได้โดยโปรแกรมไม่ค้าง นี่คือ non-blocking polling ที่เราย้ำในสไลด์ ก้าวที่ 2 และเป็นเหตุผลที่เกมจริงเอาโครงนี้ไปแทรกในลูปได้โดยเกมไม่กระตุกtopic, data = msg แกะ tuple ออกเป็นสองตัว แล้ว data.decode() (:57-58) เพราะ payload ที่วิ่งมาบนเน็ตเป็น bytes เสมอ ต้องแปลงเป็น str ก่อนอ่าน — จำกับดัก "แสดงเป็น b'...' เพราะลืม decode" ได้ไหม บรรทัดนี้แหละที่กันdeadline = time.time() + 15 แล้ว while time.time() < deadline (:53-54) คือการ จำกัดเวลาแทนวนไม่รู้จบ — เดโมนี้ตั้งใจให้จบเองใน 15 วิ จะได้ทดสอบง่าย ในเกมจริงเราจะเปลี่ยน deadline เป็นเงื่อนไข "ยังเล่นอยู่" แทนmqtt.disconnect() (:61) คือการเก็บกวาด คืนการเชื่อมต่อให้ broker อย่างเรียบร้อย ไม่ทิ้ง session ค้าง — นิสัยเดียวกับที่งาน embedded ต้องปิดของที่เปิดไว้เสมอ ออกจากงานให้เครื่องอยู่ในสถานะที่รู้แน่ห้าส่วนนี้ไต่ระดับกันยังไง:
| ส่วน | บรรทัด | แนวคิดใหม่ที่เพิ่มเข้ามา | ของเดิมที่เอากลับมาใช้ |
|---|---|---|---|
| ตั้งค่า | :15-20 |
รวมค่าที่ต้องแก้ไว้ที่เดียว (config block) | ตัวแปร + constant |
| ต่อ WiFi | :25-34 |
retry loop + ธง ok + ด่าน raise SystemExit |
for ลูป + เช็ก True/False |
| ต่อ broker | :38-44 |
ลำดับ wifi→mqtt, client_id, subscribe ก่อน publish |
ด่านกั้นเดิมของ WiFi |
| ส่งคะแนน | :48-49 |
application protocol "%s:%d" (ชื่อ:คะแนน) |
string format |
| วนรับ | :53-61 |
non-blocking poll + .decode() + จำกัดเวลา + disconnect |
while ลูป + guard เดิม |
ทั้งไฟล์คือด่านกั้นเดิมซ้ำ ๆ แค่เปลี่ยนของที่กั้น: ต่อเน็ตติดไหม, ต่อ broker ติดไหม, มีข้อความไหม เห็นจังหวะไต่ระดับนี้แล้ว น้องจะเขียนโค้ดเน็ตของตัวเองได้โดยไม่ต้องท่อง
หยุดคิดสักครู่ก่อนปิดคาบ ไม่ใช่เรื่องไวยากรณ์ แต่เรื่องมองให้ทะลุว่า "ส่งคะแนนขึ้นกระดาน" ที่เราเพิ่งทำ จริง ๆ มันคือชิ้นส่วนของอะไร
ที่มา — เน็ตวันนี้ยืนอยู่บนอะไร ย้อนไปสไลด์ "publish / subscribe — ส่งครั้งเดียว ถึงทุกบอร์ด" เราคุยเรื่อง decouple: คนส่งกับคนรับไม่ต้องรู้จักกัน broker เป็นตัวกลาง และ get_message() ที่คืน None เมื่อไม่มีของ ก็คือแนวคิดเดียวกับ non-blocking ที่เราใช้วนเช็กปุ่มในเกมมาตั้งแต่คาบต้น ๆ — "วนถามเรื่อย ๆ โดยไม่ค้าง" ส่วนโครง for i in range(6) ที่วนต่อ WiFi ก็คือ for ลูปตัวเดิมที่เราใช้มาตั้งแต่คาบแรกสุด แค่เอามากั้นการต่อเน็ต
ที่ไป — เน็ตวันนี้จะโตเป็นอะไร ในสไลด์เช็กผ่านท้ายคาบ อาจารย์บอกว่าคาบ 14 คือโปรเจกต์ปิดคอร์ส สังเกตไหมว่า pattern "อ่านค่า → ประกอบ payload → publish → ฝั่งโน้น subscribe → decode → ใช้งาน" ไม่ได้ผูกกับคำว่าคะแนนเลย เปลี่ยน my_score เป็นค่าเซ็นเซอร์ เปลี่ยนปลายทางเป็นแดชบอร์ดคลาวด์ ก็กลายเป็นระบบ IoT จริงทันที นี่คือโครงเดียวที่อุปกรณ์ IoT ทุกตัวในโลกใช้
ลองตอบคำถามพวกนี้ในใจ นี่แหละคือการเชื่อมจุดด้วยตัวเอง:
get_message() ที่คืน None เมื่อยังไม่มีของได้ไหม มันคือเรื่องเดียวกับตอนเราวนเช็กปุ่ม joystick ที่ "ไม่มีก็ผ่านไป" หรือเปล่า"board-01:1250" ขึ้น topic ได้ พรุ่งนี้เราส่ง "sensor-3:27.5" (อุณหภูมิ) ขึ้นคลาวด์ ได้ไหม เปลี่ยนแค่ค่ากับปลายทาง โครงเดิมทั้งดุ้นถ้าตอบสามคำถามข้างบนได้ว่า "อ๋อ มันคือเรื่องเดียวกัน" — นั่นคือการหยั่งรู้ที่อาจารย์อยากให้เกิด คะแนนบนกระดานวันนี้ไม่ได้เล็ก มันคือประตูบานแรกที่พาบอร์ดของเราออกสู่โลกทั้งใบ
เทคนิควันนี้ไม่ใช่แค่เกม leaderboard มันคือโครงที่ระบบจริงทั่วโลกใช้อยู่ทุกวินาที:
publish สถานะขึ้น topic ตัวเอง แล้วแอปกลาง subscribe เห็นครบ เพิ่มอุปกรณ์ใหม่ไม่ต้องแก้แอป — คือ decouple จากสไลด์ publish/subscribe เป๊ะpublish ขึ้น broker ตัวเดียว แดชบอร์ดตัวเดียว subscribe ก็มอนิเตอร์ได้หมด — คือ "เซ็นเซอร์ 100 ตัว topic เดียว" ที่พูดถึงในสไลด์ publish โดยตรงpublish พิกัด GPS เป็นระยะ ส่ง เมื่อขยับพอควร ไม่ใช่ทุกเสี้ยววินาที — ตรงกับสไลด์ "ส่งคะแนนถี่แค่ไหนพอดี" ที่ให้ส่งเมื่อค่าเปลี่ยนไม่มีข้อไหนเป็นของสมมติเลย ทุกข้อคือ pub/sub บน MQTT ที่เดินอยู่จริงในผลิตภัณฑ์ระดับโลก น้องเพิ่งเขียนโครงเดียวกับมันไปเมื่อกี้นี้เอง
ลองเอาโจทย์พวกนี้ไปคิดต่อ ไม่มีคำตอบเดียวตายตัว ทุกข้อโยงกลับไปหาโปรเจกต์จริงได้:
dict (คีย์ = ชื่อบอร์ด) แล้วอยากโชว์ Top 3 เรียงมากไปน้อย อัปเดตทุกครั้งที่มีคะแนนใหม่ จะออกแบบยังไงให้ชื่อซ้ำทับค่าเดิม ไม่ใช่เพิ่มบรรทัดใหม่เรื่อย ๆpublish "board-99:999999" มาป่วนกระดาน เราจะออกแบบ payload ให้ตรวจสอบได้ยังไง (ใบ้: เพิ่มฟิลด์ลายเซ็นง่าย ๆ หรือช่วงคะแนนที่เป็นไปได้) — โจทย์เดียวกับที่วิศวกรจริงต้องคิดเรื่องความน่าเชื่อถือของข้อมูลraise SystemExit เมื่อต่อไม่ติด แต่ถ้าเน็ตหลุดตอนเล่นอยู่ล่ะ จะเอา retry loop ของ WiFi (:25-30) มาห่อ mqtt.get_message ให้ต่อกลับเองโดยไม่รีสตาร์ตเกมยังไงpublish ยังไงให้ลื่นแต่ไม่ถล่ม broker — คิดไว้ เพราะคาบหน้าจะได้หยิบมาใช้จริงเลือกมาสักข้อ แล้วเขียนลงใบงานว่า "ถ้าเป็นเรา จะออกแบบยังไง" ไม่ต้องมีคำตอบถูก ขอแค่คิดต่อจากโค้ดที่พิมพ์เองวันนี้ — ตรงนั้นแหละที่น้องเริ่มเป็นวิศวกรระบบ ไม่ใช่แค่คนต่อเน็ตตามเฉลย
fit-css
ภาพปลายทาง: คะแนนจากหลายบอร์ดขึ้นกระดานเดียวกันผ่าน MQTT