ก่อนแตะโค้ด ขอปูภาพรวมการแปลงข้อมูลให้เห็นทั้งเส้นก่อน เพราะจุดนี้พลาดกันบ่อยที่สุด

ฝั่งส่งเราเอาตัวเลขมาทำเป็น str แล้ว publish · ระหว่างทาง MQTT ห่อเป็น UTF-8 bytes ให้เอง · ฝั่งรับจึงได้ bytes กลับมา ต้อง .decode() คืนเป็น str แล้วค่อย split(":") กับ int() แยกชื่อกับคะแนนออกมา
ดูแบบไล่ทีละขั้นจากซ้ายไปขวา ตามลำดับที่ข้อมูลถูกแปลงในแต่ละช่อง:
ถ้าจอขึ้นเป็น
b'board-01:1250'(มีb'นำหน้า) แปลว่าลืม.decode()— นี่คือบั๊กอันดับหนึ่งของคาบนี้
เทียบให้เห็นชัดใน REPL: ลืม .decode() แล้ว split จะพังทันที แต่ใส่แล้วแยกข้อมูลได้สวยงาม

ภาพจำลองหน้าจอ REPL (MicroPython) เทียบ payload ที่ลืม/ใส่ .decode() — ค่าและข้อความ error ตรงกับที่เกิดขึ้นจริงเมื่อรันบนบอร์ด
หลายบอร์ด publish / subscribe ผ่าน broker ตัวเดียว — บอร์ดไม่ต้องรู้จักกันเอง แค่รู้ topic เดียวกัน


ขวา: บอร์ดตระกูล PSoC จริง (พร้อมตัวโปรแกรม MiniProg) — ก้อนซิลิคอนเดียวกับ PSoC Edge ที่เราถืออยู่ บล็อกไดอะแกรมซ้ายคือ "ข้างใน" ของอุปกรณ์จริงตัวนี้
ที่มา: "PSoC Express Development Board 1" — Dacs, CC BY-SA 4.0, Wikimedia Commons
game/score, sensor/temp) — สมัครรับเฉพาะป้ายที่เราสนใจ1 broker + topic เป็นป้าย = ทั้งห้องเรียนคุยกันได้ผ่านตัวกลางตัวเดียว
ถ้าให้บอร์ดทุกใบต่อตรงหากันเอง (mesh) จำนวนสายจะโตแบบ กำลังสอง แต่ผ่าน broker ตัวกลางจะโตแบบ เส้นตรง
ก่อนดูกราฟ ดูที่ "รูปทรง" ของการต่อสายก่อน — ฝั่งซ้าย (mesh) ทุก node ต้องลากสายหากันเองทุกคู่ พอเพิ่ม node สายเลยพุ่ง · ฝั่งขวา (broker) เพิ่ม node ก็แค่ลากเข้าตัวกลาง เส้นเดียว:
ทีนี้พอเอาจำนวนสายไปพล็อตตาม จะเห็นเส้นแดง (mesh) แยกหนีเส้นน้ำเงิน (broker) ชัดเจน:

นี่คือเหตุผลทางวิศวกรรมที่ IoT เลือก MQTT จริง ๆ — ไม่ใช่แค่ "สะดวก" แต่เพราะมันโตแบบ ไม่ใช่
ลำดับสำคัญ: WiFi ก่อน แล้วค่อย MQTT ถ้าต่อ WiFi ผ่านเมนูบนจอไว้แล้ว บรรทัด connect จะผ่านทันที
import wifi
import mqtt
import bentogame as game
BROKER = "test.mosquitto.org" # broker สาธารณะ
TOPIC = "tesaiot/class/score" # ทุกบอร์ดใช้ topic นี้ร่วมกัน
PLAYER = "board-01" # ชื่อบอร์ดเรา ตั้งไม่ให้ซ้ำ
if not wifi.is_connected():
wifi.connect(SSID, PASSWORD) # ต่อเน็ตก่อน
mqtt.connect(BROKER, 1883, client_id=PLAYER)
mqtt.subscribe(TOPIC) # ขอฟัง topic เดียวกัน
my_score = 1250 # คะแนนที่เพิ่งเล่นจบได้
mqtt.publish(TOPIC, "%s:%d" % (PLAYER, my_score)) # "board-01:1250"
subscribe = บอก broker ว่า "มีใครส่งเข้า topic นี้ ส่งมาให้ผมด้วย"
ลำดับ 4 ขั้นต้องเป๊ะตามนี้ — WiFi ก่อน เสมอ ถ้าสลับไป mqtt.connect ก่อนต่อเน็ตจะ error ทันที:
เปิดไฟล์นี้ใน BENTO IDE แล้วกดปุ่ม Program to Device (มุมซ้ายบน) เพื่อส่งโค้ดลงบอร์ด — เป็นปุ่มเดียวกับที่เราใช้ตลอดคอร์ส:

BENTO IDE — ปุ่ม Program to Device มุมซ้ายบน · คอนโซลด้านล่างคือผลตอนกดรันลงบอร์ดจริง
รับคะแนนเพื่อนด้วย get_message() (ไม่บล็อก: ไม่มีของก็คืน None) แล้วเก็บคะแนนสูงสุดต่อชื่อ
game.start()
board = {PLAYER: my_score} # dict: ชื่อ -> คะแนนสูงสุด
game.Text("LEADERBOARD ทั้งห้อง", 40, 24, game.CYAN)
rows = [game.Text("", 60, 80 + i * 48, game.WHITE) for i in range(5)]
def redraw():
top = sorted(board.items(), key=lambda kv: kv[1], reverse=True)[:5]
for i in range(5):
if i < len(top):
name, score = top[i]
rows[i].set("%d. %-12s %6d" % (i + 1, name, score))
def update():
msg = mqtt.get_message() # (topic, bytes) หรือ None
if msg is not None:
_t, data = msg
name, score = data.decode().split(":") # bytes -> decode -> split
if int(score) > board.get(name, -1): # เก็บคะแนนสูงสุดเท่านั้น
board[name] = int(score); redraw()
game.run(update, fps=10) # BACK = ออก, START = เริ่มใหม่
game.run วน update() ให้ทุกเฟรม เราจึง poll ข้อความใหม่ตลอด กระดานอัปเดตสด
สิ่งที่ update() ทำในแต่ละเฟรม — ถ้าไม่มีข้อความก็ข้ามไปเลย (ไม่บล็อกเกม):
ผลบนจอบอร์ดหน้าตาประมาณนี้ (อันดับ 1 ไฮไลต์เหลือง คะแนนชิดขวาด้วย %6d):

ภาพจำลองหน้าจอบอร์ด สร้างตาม game.Text ในโค้ดจริง (หัวข้อ CYAN ที่ (40,24) · แต่ละแถวที่ y=80+i·48)
sorted() ทำงานทุกครั้งที่คะแนนใหม่เข้ามา sorted(...) จะเรียงทั้ง dict ใหม่ แล้ว redraw() วาด top-5 ทับของเดิม คนที่คะแนนพุ่งก็ไต่อันดับขึ้นมาให้เห็นสด ๆ

board-03 เด้งจาก 980 เป็น 1950 → sorted ดันมันขึ้นอันดับ 1 ทันทีdict แล้วให้ sorted จัดอันดับให้ทุกเฟรมเห็นภาพไหมว่า "โครงสร้างข้อมูล (dict) + อัลกอริทึมเรียง (sort)" สองอย่างนี้ทำงานร่วมกันเป็นกระดานสดได้ยังไง
รัน: เปิด codes/ex_a1_leaderboard.py ใน BENTO IDE แล้วกด Program to Device ทุกบอร์ดรัน ไฟล์อันเดียวกัน เปลี่ยนแค่บรรทัด PLAYER ให้ไม่ซ้ำ
TOPIC เป๊ะตัวเดียวกัน → กลายเป็นกระดานรวมห้องเดียว
กดปุ่ม Program to Device (เขียว มุมซ้ายบน) ในทุกบอร์ด

กับดักที่เจอบ่อย:
client_id(PLAYER) ซ้ำกัน broker จะเตะกันหลุด — ตั้งชื่อไม่ซ้ำทั้งห้อง และ topic ต้องสะกดตรงกันทุกตัวอักษร
แพตเทิร์น "หลาย node → broker → กระดานรวม" ที่เราเพิ่งทำ คือโครงเดียวกับงานจริงเป๊ะ ๆ
sensors.bmi270.acceleration())"ชื่อ:คะแนน:ด่าน" แล้ว split(":") หลายช่อง → ทำ co-op / แข่งเรียลไทม์ได้ภาพขวาคือห้องควบคุมจริง: วิศวกรหนึ่งคนเฝ้า "กระดานรวม" ที่รับค่าจากอุปกรณ์กระจายอยู่ทั้งระบบ — กระดานคะแนนของเราคือเวอร์ชันจิ๋วของหน้าจอนี้
สิ่งที่น้อง ๆ ทำวันนี้ ไม่ใช่ของเล่น — มันคือสถาปัตยกรรม pub/sub ที่ใช้จริงในระบบ IoT ระดับโรงงาน เราแค่ใส่มันลงในเกมให้สนุกก่อน
ที่มา: "Hideji Kawasaki operates the SCADA system" — U.S. Navy photo by Joe Schmitt, Public domain, Wikimedia Commons
กระดานเล็ก ๆ ที่คะแนนเพื่อนวิ่งเข้ามานี้ รวมรากฐานสี่ด้านที่เราใช้ตลอดคอร์สไว้ในงานเดียว:
Embedded / MCU — บอร์ดต่อ WiFi ขึ้น network stack กลายเป็น node หนึ่งบนเครือข่าย; update() ที่ส่งให้ game.run() ถูกเรียกตามจังหวะเฟรม เราเลย poll ข้อความใหม่ได้เรื่อย ๆ โดยไม่บล็อก
Python — เก็บคะแนนใน dict (ชื่อ → คะแนนสูงสุด); สร้างแถวด้วย list comprehension; แปลง str bytes ด้วย
.decode(); แยกข้อมูลด้วย .split(":") + int(); กัน payload เพี้ยนด้วย try/except
Algorithms — sorted(..., key=lambda kv: kv[1], reverse=True) คือการ เรียงลำดับ ; การเลือกใช้ broker แทน mesh คือ complexity vs ; ตัว pub/sub เองคือ message routing ด้วย topic
Graphics — วาง game.Text แต่ละแถวที่พิกัด (ระบบพิกัด y มาก = ล่าง); อัปเดตเฉพาะตอนข้อมูลเปลี่ยน (redraw on change) ให้จอไม่กระพริบ
คลิปเสริม: กระดานคะแนนเรียลไทม์ของเกมจริงเก็บและเรียงคะแนนหลายผู้เล่นอย่างไร — ตรงกับกระดานที่เราเพิ่งทำเป๊ะ
Create & Scale Real-Time Leaderboards (for Games & Apps) — DragonflyDB
fit-css