จุดที่ทำให้ MQTT ทรงพลัง: sender ไม่รู้จัก ว่าปลายทางมีกี่จอ มันแค่หย่อนข้อความเข้า topic — broker เป็นคนคัดลอกส่งต่อให้ทุกตัวที่ subscribe ไว้

ดูจังหวะข้อความวิ่ง: ออกจาก sender → ค้างที่ broker → แตกเข้าทั้งสองจอพร้อมกัน นี่คือ fan-out ของ pub/sub
ต่อให้มีกี่จอ ฝั่ง sender ก็ publish แค่ ครั้งเดียว ภาระการคัดลอกเป็นของ broker:

แปลเป็นภาษาคน: โค้ดฝั่งเราเท่าเดิมเป๊ะ จะมี 1 จอหรือ 50 จอก็เขียนเหมือนกัน — ต่างจากถ้าต้องต่อสายตรงหากันเอง ที่ยิ่งเพิ่มปลายทางยิ่งต้องแก้โค้ดทุกครั้ง นี่คือเหตุผลที่ระบบ IoT จริงเลือก pub/sub
ทุก step คืน True/False ให้เช็กก่อนไปต่อ ต่อ WiFi → ต่อ broker → แล้วค่อยส่ง
BROKER = "test.mosquitto.org"
PORT = 1883
TOPIC = "msg/board02" # ป้ายปลายทาง ฝั่ง display ต้องใช้ชื่อนี้เป๊ะ
MESSAGES = ["สวัสดีครับ", "ทีมแดงนำอยู่", "เริ่มรอบใหม่ได้เลย",
"พักก่อนนะ", "สู้ ๆ นะทุกคน"] # ภาษาไทยล้วน ใส่ลง list ตรง ๆ
def update():
global index
k = game.keys() # อ่านจอยรอบนี้
if k.left: # เลื่อนข้อความก่อนหน้า (วนรอบ)
index = (index - 1) % len(MESSAGES)
preview.set(MESSAGES[index]); game.sfx("move"); time.sleep_ms(180)
elif k.right: # เลื่อนข้อความถัดไป
index = (index + 1) % len(MESSAGES)
preview.set(MESSAGES[index]); game.sfx("move"); time.sleep_ms(180)
elif k.a: # กด a = ส่งข้อความที่เลือกอยู่
mqtt.publish(TOPIC, MESSAGES[index]) # payload เป็น str ส่งตรงได้
status.set("ส่งแล้ว -> " + MESSAGES[index]); game.sfx("select")
time.sleep_ms(180) หลังเลื่อน = กันการกดค้างแล้วเลื่อนรัวเกินจนคุมไม่ได้

อ่านจอยหนึ่งรอบ แล้วแตกเป็น 3 ทาง: left/right เลื่อน index, a ส่ง — วนซ้ำทุกเฟรม
เสียงในคาบนี้ — ป้ายข้อความข้ามบอร์ดใช้เสียง UI ของ
game.sfx: เลื่อนจอยเลือกข้อความได้moveกดapublish ส่งออกได้select(กดเล่นฟังได้จริง):
ฝั่งนี้ต้อง subscribe topic เดียวกันก่อน แล้ว get_message() ทุกเฟรมเพื่อรับข้อความล่าสุด
mqtt.subscribe(TOPIC) # ขอฟัง "msg/board02" = พร้อมรับป้าย
board = game.Text("(ว่าง)", 30, 150, color=game.YELLOW) # ป้ายใหญ่กลางจอ
def update():
msg = mqtt.get_message() # คืน (topic, payload) bytes หรือ None
if msg is not None:
topic, data = msg
text = data.decode() # payload เป็น bytes ต้อง decode ก่อน
board.set(text) # อัปเดตป้ายเป็นข้อความล่าสุด
game.sfx("point")
time.sleep_ms(50) # พักนิด ไม่ poll รัวเกิน
จุดพลาดบ่อย: ลืม .decode() แล้วจอขึ้นเป็น b'...' — payload มาเป็น bytes เสมอ ต้อง decode ก่อนโชว์

สามจังหวะของฝั่งรับ: poll → ถ้าไม่ None ก็ decode → วาดป้าย; ถ้า None ก็ข้ามไปไม่ค้างเกม
sleep_ms(50) นั้นมาจากไหน — ดีเลย์ของป้ายที่ใส่ time.sleep_ms(50) ไม่ได้สุ่มมา มันคุม ความเร็วที่ป้ายอัปเดต โดยตรง เพราะป้ายจะเห็นข้อความก็ต่อเมื่อ get_message() ถูกเรียกรอบถัดไป ดีเลย์แย่สุดจึงเท่ากับช่วง poll บวกเวลาเดินทางผ่านเน็ต:

sleep_ms(50) → ดีเลย์ราว 80 ms คนแทบไม่รู้สึก แต่ยังเหลือเวลาให้ CPU หายใจ ไม่ poll รัวจนเปลือง ถ้าตั้งสั้นกว่านี้ป้ายไวขึ้นนิดเดียวแต่กินทรัพยากรเพิ่มชัด — ของแบบนี้คือการ "จูน" ที่วิศวกร embedded ทำกันจริง
นี่คือของดีที่ทำให้ป้ายนี้สนุก: ส่งข้อความเป็นภาษาไทยได้ตรง ๆ ฝั่ง sender ใส่ str ไทยลง MESSAGES ฝั่ง display .decode() แล้วโยนเข้า game.Text จอก็วาดให้ครบ

ซ้าย: หน้าตาป้ายที่โค้ดเราวาด · ขวา (ภาพประกอบของจริง): บอร์ด MCU จริงขับจอ OLED ขึ้นตัวอักษร — หลักการเดียวกับที่บอร์ดเราวาดไทยลงจอ — "TTGO Lora32 Node" — -stk, CC BY-SA 4.0, Wikimedia Commons
ตั้งแต่ firmware v1.2.0 ตัวเครื่องมีฟอนต์ไทย + จัด "สระซ้อน/วรรณยุกต์" (cluster shaping) ให้เอง คำที่วางสระบน-ล่างซ้อนกันก็ขึ้นถูกตำแหน่ง ลองส่งดู: ปั๊ม · เกี๊ยว · ผู้รู้ · เปรี้ยว — ไม้โท ไม้ตรี สระอู สระเอีย วางครบทุกตัว
เราเลยแก้
MESSAGESเป็นไทยล้วนได้สบาย เช่น"สวัสดีครับ","ทีมแดงนำอยู่","เริ่มรอบใหม่ได้เลย"— payload ก็ยังเป็นstrธรรมดา MQTT ส่งเป็น UTF-8 bytes ให้อัตโนมัติ ฝั่งรับ.decode()กลับมาเป็นไทยเหมือนเดิม
โปรเจกต์นี้ต้องใช้ 2 บอร์ด (หรือจับคู่กับเพื่อน): บอร์ดหนึ่งเป็น sender อีกบอร์ดเป็น display
ex_c2_display.py แล้วกด Program to Device → จอขึ้น "รอข้อความเข้า ..."ex_c2_sender.py แล้วกด Program to Device → เลื่อนจอยเลือก แล้วกด a

ซ้าย: ผังต่อ 2 บอร์ด (sender + display) · ขวา (ภาพประกอบของจริง): บอร์ดวิทยุ MCU ที่ส่งข้อความหากันข้ามอากาศจริง — สเกลเล็กแบบเดียวกับ 2 บอร์ดของเรา — "Meshtastic T-Beam" — Chiffre01, CC0, Wikimedia Commons
เช็กผ่าน: เลือกข้อความไทยต่างกัน 3 อัน กด a ทีละอัน แล้วบอร์ด display ขึ้นเป็นไทยครบทั้ง 3 (สระ/วรรณยุกต์ต้องวางถูกตำแหน่ง) — ทั้งคู่ต้องใช้
TOPIC=msg/board02ตรงกันเป๊ะ

ที่มา: "Andon-Board in einem Produktionswerk" — MicroSYST Systemelectronic GmbH, CC BY-SA 4.0, Wikimedia Commons
นี่คือ ป้าย Andon ในโรงงานจริง แขวนเหนือสายการผลิต โชว์ค่าเป้าหมาย/ค่าจริง (Soll/Ist) กับสถานะไฟเขียว-เหลือง-แดง ของแต่ละไลน์ — ศูนย์ควบคุมตัวเดียวส่งข้อมูลขึ้นทุกป้ายในโรงงาน หลักการเดียวกับที่เราเพิ่งเขียน: publish เข้า topic เดียว ป้ายที่ subscribe ขึ้นพร้อมกัน ต่างกันแค่สเกล
เปลี่ยนข้อความทักทายเป็นตัวเลขยอดผลิต เปลี่ยน
game.Textสีเดียวเป็นไฟสถานะ 3 สี โครงโค้ดที่น้องมีอยู่ในมือก็กลายเป็นป้าย Andon ได้เลย
โปรเจกต์เล็ก ๆ นี้แตะรากฐาน 4 ด้านที่ใช้สร้างของจริงครบ:
MESSAGES คือ list เลื่อน index แบบวนด้วย % len(...); mqtt.get_message() คืน tuple (topic, bytes) แล้ว unpack; .decode() แปลง bytes → str (เรื่อง type ที่เจอตั้งแต่คาบแรก)T_poll ให้ดีเลย์/โหลดสมดุล คือ pattern เดียวกับลูปอ่านเซนเซอร์; topic routing คือการ "จับคู่ที่อยู่" แบบง่าย ๆgame.Text วาดสตริงไทยลงเฟรมบัฟเฟอร์ พร้อม cluster shaping (จัดสระ/วรรณยุกต์ตามพิกัด) ก่อนส่งขึ้นจอ
เห็นไหมว่า "ป้ายไฟข้ามห้อง" สนุก ๆ จริง ๆ แล้วคือ network + โครงสร้างข้อมูล + การวาดภาพ ทำงานด้วยกัน — พอจับหลักได้ น้องต่อยอดไปทำผลิตภัณฑ์ IoT จริงได้เลย
ของวันนี้คือโครงเดียวกับ ป้ายไฟอุตสาหกรรม และ เกม multiplayer — เปลี่ยนแค่ payload กับ topic เท่านั้นเอง
ต่อยอดที่ลองได้ (เลือก 1):
MESSAGES ลองคำที่มีสระซ้อนเยอะ ๆ ดูว่าจอวางครบไหม"ทีมแดง:สู้ ๆ" แล้วฝั่ง display split(":") โชว์ว่าใครส่งmsg/board02, msg/board03 แล้วเลือกปลายทางด้วยปุ่ม bทั้ง 3 ข้อใช้ โครงเดิม ทั้งหมด แตะแค่ payload กับ topic เท่านั้น — ไม่ต้องเขียนระบบเชื่อมต่อใหม่:
โยงโลกจริง:
น้อง ๆ เพิ่งทำสิ่งที่โรงงานจริงใช้กันทุกวัน ด้วยโค้ดไม่ถึง 70 บรรทัด — นี่คือพลังของ MQTT
fit-css