หัวใจคือ topic poll/q1: ใครส่งเข้า topic นี้ ทุกคนที่ subscribe จะได้รับ
payload เราตั้งใจให้ สั้นที่สุด คือตัวอักษรเดียว ฝั่งรับจึงแค่ decode() แล้วเอาไปนับได้เลย
แนวคิด topic เดียวกระจายถึงทุกคน ไม่ได้มีแค่ในเกมโหวตของเรา — IoT จริงใช้กันทั้งโลก ภาพนี้เป็นตัวอย่างบ้านอัจฉริยะ: ปลั๊กกับเทอร์โมมิเตอร์ publish ค่าขึ้น broker ส่วนมือถือกับโน้ตบุ๊ก subscribe มาดู

ที่มา: "MQTT single broker multiple listener" — Ademant, CC BY-SA 4.0, Wikimedia Commons
สังเกต subscribe: /plug1/# กับ /+/current — นั่นคือ wildcard ขอรับหลาย topic ทีเดียว ตอนนี้เราใช้ topic เดียว poll/q1 ก่อน พอเพิ่มหลายคำถามค่อยใช้ wildcard รับทุกข้อรวดเดียวได้
ถ้าไม่มีตัวกลาง บอร์ดทุกคู่ต้องต่อหากันเอง จำนวนสายโตไว้มาก เทียบ N บอร์ด:

นี่คือเหตุผลที่ระบบ IoT เลือก pub/sub: voter จะมีกี่เครื่องก็ได้ ต่างคนต่างยิงเข้า
poll/q1เส้นเดียว ไม่ต้องรู้จักกันเลย — broker จัดการกระจายให้
ก่อนเดินโค้ด ลองดูภาพ "เวลา" ว่าโหวต 1 ครั้งวิ่งผ่านระบบยังไง (บนลงล่าง = เวลาเดินไปข้างหน้า)

ฝั่ง tally subscribe ค้างไว้ก่อน พอ voter publish broker ก็ส่งต่อให้ทันที — ทุกบรรทัดในภาพ map ตรงกับโค้ดสองหน้าถัดไปเป๊ะ ๆ
ฝั่ง voter จับขอบ "ดันจอย 1 ครั้ง = เลื่อน 1 ช่อง" และ "กด A 1 ครั้ง = ส่ง 1 โหวต"
index = 0 # ตำแหน่งตัวเลือกที่กำลังชี้อยู่
prev_a = False # จำสถานะปุ่ม A รอบก่อน (จับขอบขาลง)
prev_dir = False # กันเลื่อนรัวตอนดันจอยค้าง
while True:
k = game.keys()
if k.back: # BACK = ออก
game.clear()
break
moving = k.left or k.right
if moving and not prev_dir: # เลื่อนทีละ 1 ต่อการดัน 1 ครั้ง
index = (index + (1 if k.right else -1)) % len(CHOICES)
pick.set("ตัวเลือก: " + CHOICES[index])
prev_dir = moving
if k.a and not prev_a: # กด A = ยืนยันโหวต
mqtt.publish(TOPIC, CHOICES[index]) # payload เป็น str เช่น "A"
prev_a = k.a
time.sleep_ms(40)
เทคนิคเดิมจากคาบปุ่ม: เทียบสถานะรอบก่อน (prev_a) เพื่อนับ "กดครั้งเดียว" ไม่ใช่กดค้างแล้วรัว

ภาพบน: เส้นสัญญาณปุ่มตามเวลา จุดแดง "นับ!" เกิดเฉพาะ ตอนเปลี่ยนจากปล่อย→กด (ขอบขาขึ้น) กดค้างยาวแค่ไหนก็ไม่นับซ้ำ — นี่คือเหตุผลที่ต้องจำ prev_a
ฝั่ง tally เก็บคะแนนใน dict หนึ่งตัว แล้ววาดแท่งใหม่ทุกครั้งที่มีโหวตเข้ามา
votes = {c: 0 for c in CHOICES} # dict นับคะแนน เริ่มที่ 0 ทุกตัว
mqtt.subscribe(TOPIC) # ขอฟังทุกโหวตในห้องนี้
while True:
msg = mqtt.get_message() # คืน (topic, bytes) หรือ None (ไม่บล็อก)
if msg is not None:
_topic, data = msg
choice = data.decode().strip() # payload เป็น bytes ต้อง decode
if choice in votes: # นับเฉพาะตัวเลือกที่รู้จัก
votes[choice] += 1
redraw() # แท่งสูง = 1 + คะแนน * UNIT
time.sleep_ms(50)
get_message() ไม่บล็อก — ยังไม่มีของก็คืน None จอจึงไม่ค้าง วนเช็กไปเรื่อย ๆ ได้

ไล่ตามลูกศร: bytes b"B" → decode() ได้ "B" → votes["B"] += 1 → แปลงเลขเป็นพิกเซลด้วย h = 1 + votes[c]*UNIT แล้ววาดแท่งใหม่ — ตัวเลขใน dict กลายเป็นความสูงบนจอตรง ๆ
ใช้ 2 บอร์ด (ต่อ WiFi เดียวกัน, ใช้ TOPIC เดียวกันให้เป๊ะ):
| บอร์ด | ไฟล์ที่รัน | หน้าที่ |
|---|---|---|
| เครื่องที่ 1 | codes/ex_b4_voter.py |
รีโมตโหวต — กดเลือกแล้วส่ง |
| เครื่องที่ 2 | codes/ex_b4_tally.py |
จอประกาศผล — นับ + วาดแท่ง |
ขั้นตอน:

สองบอร์ดต่อ WiFi เดียวกันแล้ว (มุมซ้ายบนขึ้น Wi-Fi ต่อแล้ว): ซ้าย = voter กด A ส่ง · ขวา = tally แท่งขึ้นตามโหวต · ที่มา: BENTO game console (course asset)
หน้าจอ tally ที่ควรเห็น: ทุกครั้งที่โหวตเข้ามา แท่งของตัวเลือกนั้นเด้งสูงขึ้น ทันที ไม่ต้องรีเฟรช

มีบอร์ดเดียวก็ลองได้: รัน tally ไว้ แล้วโหวตจากแอป MQTT บนมือถือ ส่ง
"A"ไปpoll/q1ก็เห็นแท่งขึ้นเหมือนกัน
"รีโมตโหวต" ที่เราทำ ก็คือสิ่งเดียวกับ audience response clicker ที่ใช้จริงในงานสัมมนา/ห้องเรียน (ในภาพ) — กดเลือกตัวเลือก ผลขึ้นจอรวมทันที ต่างกันแค่ของเราใช้ MQTT แทนคลื่นวิทยุเฉพาะของมัน
ระบบเล็ก ๆ วันนี้คือต้นแบบของของจริงหลายอย่าง:
poll/q2, poll/q3 เปลี่ยนคำถามได้หลายข้อ เหมือน Kahootหลักการ pub/sub ที่น้อง ๆ จับได้วันนี้ คือหัวใจเดียวกับระบบ IoT ระดับโรงงาน — ต่างกันแค่สเกลและความปลอดภัย
ที่มา: "CLiKAPAD Prioritizer Audience Response Keypad" — CLiKAPAD, CC BY-SA 3.0, Wikimedia Commons
เกมโหวตเล็ก ๆ คาบนี้แตะรากฐานที่วิศวกรฝังตัวใช้จริงพอดี:
votes = {c: 0 for c in CHOICES} คือ hash map นับความถี่ (frequency count) แล้ว votes[choice] += 1 คือการอัปเดตค่าผ่าน key — โครงสร้างข้อมูลพื้นฐานที่ใช้ได้ทุกที่ ไม่ใช่แค่เกมget_message() คืน None เมื่อยังไม่มีของ จอจึงไม่ค้าง วน poll สลับงานอื่นได้ คือหัวใจของ superloop + polling บน MCU ที่ไม่มี OSh = 1 + votes[c] * UNIT) คือการ map ข้อมูล → พิกัดหน้าจอ หลักการเดียวกับ dashboard/กราฟทุกตัวPub Sub Model | MQTT Essentials Part 3 — HiveMQ
fit-css