ภาพพื้นหลังปกบทเรียน

บทเรียน 1.5 — ค่าออกไป คำสั่งกลับมา: MQTT บน broker สาธารณะ

บอร์ดคุยกับโลก · ค่าที่วัดบนโต๊ะนี้ไปโผล่บนเครื่องคนอื่น แล้วคำสั่งจากที่ไกลกลับมาสั่งของบนโต๊ะเรา

โมดูล 1 — แอปพลิเคชันบนจอที่มีอยู่แล้ว

ต่อจากบทเรียน 1.4 — บอร์ดออกจากโต๊ะ: ต่อ WiFi ครั้งแรก

TESA Open Knowledge · © 2026 สมาคมสมองกลฝังตัวไทย (TESA) · ดัดแปลงจาก AIoT in Action (AIC มหาวิทยาลัยบูรพา) · CC BY-NC 4.0

วันนี้ข้อความเดินสองทาง — บอร์ดใช้ 1883 เบราว์เซอร์ใช้ wss 8884

สองทางคนละพอร์ต แต่ไปเจอกันที่ broker ตัวเดียวกัน หัวข้อเดียวกัน บอร์ดของทีม ไฟล์ 05 · 06 mqtt ไม่เข้ารหัส broker.hivemq.com สาธารณะ ไม่มีรหัสผ่าน bento-aiot/team03/... เบราว์เซอร์ my_first_reader.html ไม่ต้องลงโปรแกรม TCP 1883 wss 8884 Emulator: wss 8884 ต่อ hivemq จริงผ่าน wss และเติม -emu ท้าย client_id ไม่เตะบอร์ดของทีม ต่อไม่ได้ภายใน 5 วินาที ถอยไปใช้ broker จำลองในตัว และบอกใน Console

บอร์ดพูด MQTT ธรรมดาที่พอร์ต 1883 ส่วนหน้าเว็บเปิดสาย TCP ดิบไม่ได้ จึงพูด MQTT ผ่าน WebSocket ที่ wss://broker.hivemq.com:8884/mqtt ทั้งสองทางลงหัวข้อชุดเดียวกัน broker ส่งต่อให้เองโดยไม่สนว่าแต่ละฝั่งมาทางไหน

วัดแล้ว 24 ก.ย. 2026 จาก Mac บนโต๊ะผู้สอน ไม่ใช่จากเน็ตขององค์กร: 1883 ไป wss และ wss ไป 1883 ส่งถึงทั้งคู่ ไปกลับ 188-191 ms · ยังไม่ได้วัด: ยังไม่มีใครรันไฟล์ 05 กับ 06 บนบอร์ดกับ broker นี้ และยังไม่รู้ว่าเน็ตขององค์กรปล่อยพอร์ต 1883 กับ 8884 ออกไปหรือไม่

ตัวสำรอง ใช้เมื่อผู้สอนประกาศเท่านั้น: บอร์ด BROKER = "test.mosquitto.org" พอร์ต 1883 · หน้าเว็บ wss://test.mosquitto.org:8081/mqtt (วัดแบบเดียวกัน 185-201 ms) · อย่าใช้ broker.mqttdashboard.com ใบรับรองไม่ตรงชื่อ หน้าเว็บต่อไม่ได้

ผู้สอน ก่อนเข้าห้อง ให้บอร์ดของผู้สอนต่อ Hotspot มือถือ (WiFi ขององค์กรต้อง login บอร์ดใช้ไม่ได้) เปิดหน้ารวม https://advance-innovation-centre-aic.github.io/embedded-systems-for-aiot-developer/examples/web/mqtt_dashboard.html แล้วให้บอร์ดหนึ่งตัวรัน 05_value_leaves_the_board.py สักใบ การ์ดของทีมขึ้น = ทั้งสองทางผ่าน · ไม่ขึ้น = ลองตัวสำรอง

TESA Open Knowledge · © 2026 สมาคมสมองกลฝังตัวไทย (TESA) · ดัดแปลงจาก AIoT in Action (AIC มหาวิทยาลัยบูรพา) · CC BY-NC 4.0

broker สาธารณะ: ใครก็อ่านได้ ใครก็เขียนได้

broker นี้ไม่มีรหัสผ่าน ใครรู้ชื่อหัวข้อก็อ่านและเขียนได้ทันที หัวข้อไม่ซ้ำใคร bento-aiot/team03/... ห้าม subscribe # เท่ากับขอรับทั้งโลก ห้ามส่งความลับ ไม่เข้ารหัส ใครก็อ่านได้ client_id ไม่ชน ชนได้กับทุกคนในโลก
  • หัวข้อต้องไม่ซ้ำใคร ทุกคนใช้ชุดเดียว bento-aiot/<ทีม>/telemetry (บอร์ดส่งค่า) · bento-aiot/<ทีม>/event (เหตุการณ์ เริ่มใช้บทเรียน 2.1–2.3) · bento-aiot/<ทีม>/cmd (คำสั่งเข้าบอร์ด) ผู้เรียนคนอื่นก็ใช้ broker และหัวข้อชุดนี้ จึงตั้ง TEAM เป็นรหัสที่ไม่ซ้ำใคร a-z 0-9 ยาว 4–16 ตัว เช่นชื่อเล่นต่อด้วยเลขสุ่ม 4 หลัก (nok4821) · ถ้าเรียนเป็นกลุ่ม ผู้จัดอาจแจก team01 ถึง team19
  • ห้าม subscribe # เครื่องหมายนี้แปลว่าทุกหัวข้อบน broker ข้อความของคนแปลกหน้าทั้งโลกจะไหลเข้ามา และกล่องรับของบอร์ดมีช่องเดียว ใบใหม่ทับใบเก่า · หน้าเว็บของทีมฟัง bento-aiot/team03/# หน้ารวมของผู้สอนฟัง bento-aiot/+/telemetry
  • ห้ามส่งความลับ ไม่ว่ารหัส WiFi ชื่อจริง หรือเบอร์โทร ทุกใบวิ่งแบบไม่เข้ารหัส และใครที่ subscribe หัวข้อเดียวกันก็เห็น
  • client_id ชนกันได้กับทุกคนบนอินเทอร์เน็ต ไม่ใช่แค่เพื่อนในห้อง ชนเมื่อไร broker เตะตัวเก่าออก (วัดแล้วว่าเป็นแบบนี้ทุกตัว) ไฟล์จึงตั้ง DEVICE_ID = "bento-aiot-" + TEAM ยาว 17 ตัวอักษร (เฟิร์มแวร์ตัดที่ 31) ส่วนหน้าเว็บสุ่ม web- ตามด้วยเลขสุ่มทุกครั้ง จึงไม่เตะบอร์ด

ใครก็ส่งเข้า bento-aiot/team03/cmd ได้ ไม่ใช่แค่หน้าเว็บของเรา — นี่คือเหตุผลที่ไฟล์ 06 ไม่เชื่อคนส่งเลยสักบรรทัด และคือเหตุผลที่บทเรียน 4.4–4.6 กับ 4.7–4.9 ต้องมีเรื่องสิทธิ์และการเข้ารหัส

TESA Open Knowledge · © 2026 สมาคมสมองกลฝังตัวไทย (TESA) · ดัดแปลงจาก AIoT in Action (AIC มหาวิทยาลัยบูรพา) · CC BY-NC 4.0

ไฟล์ 05 · 05_value_leaves_the_board.py — บันไดสามขั้นที่ห้ามสลับ

1 · WiFi ต้องให้ IP ก่อน ไม่มีเลขที่อยู่ = ไปต่อไม่ได้เลย 2 · แนะนำตัวกับ broker mqtt.connect(BROKER, port=1883) 3 · ส่งของจริงออกไป mqtt.publish(TOPIC, body) ขั้นที่ยังไม่ผ่าน ต้องเห็นบนจอว่ายังไม่ผ่าน ไม่ใช่ค้างเงียบ
    linked = mqtt.connect(BROKER, port=1883, client_id=DEVICE_ID, keepalive=60)
...
    payload = {"id": TEAM,          # คีย์สั้นตัวเล็ก มี id กับ n เสมอ
               "n": i,
               "knob": knob,
               "az": az,
               "uptime_s": time.ticks_ms() // 1000}
    body = json.dumps(payload)      # dict ของเรา -> ข้อความที่ทุกภาษาอ่านออก
...
    try:
        ok = mqtt.publish(TOPIC, body)
    except OSError:                 # สายหลุดแล้ว publish ไม่คืน False มันโยน OSError
        ...

ป้ายสามขั้นบนจอไล่เปลี่ยนจากเทาเป็นเขียวตามลำดับ ขั้นไหนไม่ผ่านจะเป็นแดงพร้อมบอกเหตุผล แล้วโปรแกรมจบตรงนั้นอย่างสุภาพ ไม่ค้างรอ นี่คือแบบที่โปรแกรมหน้างานต้องเขียน ทุกทางออกของไฟล์นี้เขียนบนจอไว้เสมอว่าไปติดที่ขั้นไหน

json.dumps() แปลง dict ของเราเป็นข้อความ ทำให้เครื่องปลายทางจะเขียนด้วยภาษาอะไรก็อ่านออก — นี่คือเหตุผลที่ IoT ทั้งโลกส่ง JSON กันไปมา ไม่ใช่ส่งโครงสร้างข้อมูลของภาษาใดภาษาหนึ่ง

mqtt.connect() ใช้ชื่อ username= ไม่ใช่ user= ใส่ผิดได้ TypeError ทันที · และ client_id ที่ซ้ำกับใครก็ตามบน broker เดียวกัน ทำให้ผลัดกันเตะกันออกโดยไม่มีข้อความเตือน · บน broker สาธารณะ "ใครก็ตาม" คือทั้งอินเทอร์เน็ต

TESA Open Knowledge · © 2026 สมาคมสมองกลฝังตัวไทย (TESA) · ดัดแปลงจาก AIoT in Action (AIC มหาวิทยาลัยบูรพา) · CC BY-NC 4.0

ไฟล์ 05 (ต่อ) — ค่าที่ส่งออกไปต้องเป็นค่าจริง ไม่ใช่ตัวเลขสุ่ม

knob = -1                           # -1 = รอบนี้อ่านลูกบิดไม่ได้
az = -99.0                          # -99 = อ่านค่าเอียงไม่ได้
try:
    s = sensors.snapshot()          # ของเดิมจาก m01-ui-application/l03-inside-the-box/examples/12_every_sense_at_once.py
    if "pot" in s:                  # ถามด้วย in ก่อนหยิบ รอบที่ไม่มีคีย์จะไม่ตาย
        knob = int(s["pot"]["percent"])
    if "bmi270" in s:
        az = round(s["bmi270"]["az"], 2)   # m/s^2 วางราบราว 9.8
except OSError:
    pass                            # อ่านไม่ได้รอบนี้ ไม่ใช่เหตุให้หยุดส่ง ค่าข้างบนบอกให้รู้แล้ว
ใบที่ 7 ที่เพิ่งออกจากบอร์ดไปเมื่อกี้ {"id": "team03", "n": 7, "knob": 63, "az": 9.79, "uptime_s": 41} หมุนลูกบิดหรือเอียงบอร์ดที่โต๊ะนี้ แล้วดูเลขบนหน้าเว็บของทีมขยับตาม ค่าที่พิสูจน์ได้ด้วยมือตัวเอง ต่างจากตัวเลขสุ่มที่ใครก็เถียงได้

ไฟล์นี้ส่งค่าจริงสองค่า ลูกบิด (Eva Kit: ลูกบิดสีน้ำเงิน · Dev Kit: VR1 — sensors.pot อ่านตัวนี้ตัวเดียว) กับค่าเอียงแกน z จาก BMI270 ทั้งสองบอร์ดมีทั้งคู่ คนหนึ่งหมุน อีกคนเอียง คนที่นั่งดูอีกฝั่งจึงพิสูจน์ได้ด้วยมือตัวเองว่าขยับที่นี่แล้วเลขที่โน่นขยับตาม ไม่ใช่ตัวเลขสุ่มที่ใครก็เถียงได้

mqtt ของบอร์ดไม่เข้ารหัส และ broker วันนี้เป็นของสาธารณะ ห้ามส่งของที่เป็นความลับ (บทเรียน 4.7–4.9 ค่อยย้ายไปทางเข้ารหัส) · เฟิร์มแวร์ส่งแบบ retain ไม่ได้ หน้าเว็บที่เปิดช้าเห็นแค่ใบถัดไป ไฟล์จึงส่ง 60 ใบ ใบละ 2 วินาที

ตาคุณ อยู่ท้ายไฟล์ เปิดหน้าเว็บของทีม หมุนลูกบิดหรือเอียงบอร์ด จดว่าเห็นใบแรกที่ n เท่าไร แล้วลองตั้ง TEAM ชนกับทีมข้าง ๆ ชั่วคราว ดูว่าใครถูกเตะออก

TESA Open Knowledge · © 2026 สมาคมสมองกลฝังตัวไทย (TESA) · ดัดแปลงจาก AIoT in Action (AIC มหาวิทยาลัยบูรพา) · CC BY-NC 4.0

เปิดหน้าเว็บอ่านค่าของทีม — my_first_reader.html

1 · เปิดลิงก์ของหลักสูตร ในเบราว์เซอร์ใดก็ได้ 2 · ต่อท้าย ?team= รหัสเดียวกับ TEAM ในไฟล์ 05 06 3 · กด Enter ไม่ต้องบันทึกไฟล์ 4 · สถานะ ต่อแล้ว รอใบถัดไปจากบอร์ด เปิดได้ทุกเครื่องที่มีเบราว์เซอร์ รวมถึงมือถือ ไม่ต้องติดตั้งอะไร

ลิงก์ของหน้าอ่านค่า เปลี่ยน team05 ท้ายลิงก์เป็นรหัสของคุณ · หน้านี้บนเว็บ AIC รับเฉพาะรหัสรูป teamNN ถ้าใช้รหัสของตัวเองอย่าง nok4821 ให้ดาวน์โหลด my_first_reader.html ของรีโพนี้ไปเปิดในเบราว์เซอร์แทน

https://advance-innovation-centre-aic.github.io/embedded-systems-for-aiot-developer/examples/web/my_first_reader.html?team=team05

// ถ้าดาวน์โหลดไฟล์ไปเปิดเอง แก้บรรทัดนี้แทนการต่อท้ายลิงก์
let TEAM = "teamXX";          // รหัสเดียวกับ TEAM ในไฟล์ Python เช่น "nok4821" (เรียนเป็นกลุ่มใช้เลขที่ผู้จัดแจก เช่น "team05")
  • หน้านี้ฟัง bento-aiot/<TEAM>/# แล้ววาดหนึ่งกล่องต่อหนึ่งคีย์ของ JSON (knob az n ...) และมีปุ่มส่งเสียงกับเปิดปิดไฟ LED 0 ที่ส่งเข้า .../cmd ให้ไฟล์ 06
  • เปิดหน้านี้หลังบอร์ดส่งไปแล้ว จะว่างจนกว่าใบถัดไปมาถึง เพราะเฟิร์มแวร์ส่งแบบ retain ไม่ได้ broker จึงไม่เก็บใบล่าสุดไว้ให้ ไฟล์ 05 ส่งทุก 2 วินาทีนานสองนาทีด้วยเหตุนี้
  • ผู้สอนฉายหน้ารวม https://advance-innovation-centre-aic.github.io/embedded-systems-for-aiot-developer/examples/web/mqtt_dashboard.html ซึ่งฟัง bento-aiot/+/telemetry กับ bento-aiot/+/event เห็นทุกทีมเป็นการ์ด และส่งคำสั่งเข้า bento-aiot/<ทีม>/cmd ได้
  • Emulator ต่อ broker จริงได้ รันไฟล์ 05 06 บน Emulator ใน ide.tesaiot.dev แล้วข้อความขึ้นบนหน้าเว็บของทีมจริง และคำสั่งจากหน้าเว็บวิ่งกลับมาที่ Emulator ได้ (ทดสอบแล้วทั้งสองทาง) · Emulator เติม -emu ท้าย client_id เอง จึงไม่เตะบอร์ดจริงของทีม · ถ้าต่อไม่ได้ภายใน 5 วินาที มันถอยไปใช้ broker จำลองในตัวและบอกใน Console

ไม่มีไฟล์หน้าเว็บของเรา ใช้หน้าทดลองของผู้ให้บริการแทนได้ https://www.hivemq.com/demos/websocket-client/ ตั้ง host broker.hivemq.com port 8884 เปิด SSL แล้ว subscribe bento-aiot/<ทีมของคุณ>/#

TESA Open Knowledge · © 2026 สมาคมสมองกลฝังตัวไทย (TESA) · ดัดแปลงจาก AIoT in Action (AIC มหาวิทยาลัยบูรพา) · CC BY-NC 4.0

ไฟล์ 06 · 06_command_comes_back.py — คำสั่งเดินทางกลับมา

if not mqtt.subscribe(TOPIC_CMD):   # ต้องมาหลัง connect() เสมอ และต้องทำซ้ำถ้าสายหลุดแล้วต่อใหม่
    stop_here("subscribe ไม่ผ่าน", "broker ไม่ยอมให้ฟังหัวข้อ " + TOPIC_CMD)
...
    msg = mqtt.get_message()        # ไม่บล็อก คืน None ทันทีเมื่อยังไม่มีอะไรมา
...
    if msg is not None:
        ...
        topic, raw = msg            # topic เป็น str ส่วน payload เป็น bytes
        ...
            cmd = json.loads(raw.decode())  # ต้อง .decode() ก่อนเสมอ
        ...
        action = cmd.get("cmd", "")
        ...
        elif action == "led":
            ...
                led = gpio.led(n)   # ของบนโต๊ะเราขยับ เพราะคนที่อยู่คนละที่พิมพ์มา
                if on:
                    led.on()
กล่องรับมีช่องเดียว ใบที่สองที่มาถึงก่อนเราหยิบใบแรก ทับใบแรกทิ้งไปเลย ไม่ได้ต่อคิว ลูปจึงห้ามหลับยาว ต้องถามซ้ำถี่ ๆ คนส่งพิมพ์มั่วได้เสมอ ของที่ไม่ใช่ JSON ต้องไม่ทำให้โปรแกรมตาย เลขดวงไฟที่บอร์ดนี้ไม่มี ต้องกันเอง ข้อความยาวเกินป้าย ต้องตัดก่อนเสมอ

ไฟล์ 05 ส่งออกอย่างเดียว ไฟล์นี้เติมทางกลับให้ครบวง คำสั่งที่บอร์ดรู้จักมีสาม beep ให้ร้อง led ให้ไฟติดหรือดับ และ say ให้ขึ้นข้อความบนจอ ทุกคำสั่งมาในรูป JSON เช่น {"cmd":"led","n":0} — n คือดัชนีดวงตาม gpio.board_info()["led_names"] ของบอร์ดนั้น ถ้าสั่งดวง 0 แล้วมองไม่เห็นบนบอร์ดของทีม (บน Dev Kit ดวง 0 คือ LED1 บนโมดูล) ให้ลอง n ของดวงที่ชื่อขึ้นต้นด้วย RGB_ จากช่อง LED ของหน้ารวม mqtt_dashboard.html เพราะหน้าอ่านค่าของทีมมีปุ่มแค่ดวง 0 · วันนี้คนส่งคือปุ่มบนหน้าเว็บ my_first_reader.html ซึ่งส่งเข้า bento-aiot/<ทีม>/cmd

ครึ่งหนึ่งของไฟล์นี้คือ การไม่เชื่อคนส่ง เพราะบน broker สาธารณะ ใครที่รู้ชื่อหัวข้อก็ส่งเข้ามาได้ ข้อความจึงมาจากคนที่เราไม่รู้จักได้จริง เขาอาจพิมพ์ผิด อาจส่งของที่ไม่ใช่ JSON หรืออาจสั่งดวงไฟที่บอร์ดนี้ไม่มี ทั้งสามกรณีต้องจบลงที่ข้อความบนจอ ไม่ใช่ที่โปรแกรมตาย · ก่อนจบ ไฟล์นี้ปิดไฟทุกดวงเสมอ ไม่ทิ้งบอร์ดไว้ในสถานะที่คำสั่งสุดท้ายบังเอิญตั้งไว้ — เป็นนิสัยที่ระบบซึ่งต้องแขวนอยู่หน้างานหกเดือนขาดไม่ได้

นี่คือคำตอบของเกมต้นบทเรียน ทั้งห้องส่งพร้อมกัน แต่บอร์ดถามกล่องทุก 100 ms และกล่องมีช่องเดียว ใบที่มาถึงระหว่างนั้นทับกันเอง · ตาคุณ อยู่ท้ายไฟล์ ตั้ง POLL_MS = 3000 แล้วกดปุ่มบนหน้าเว็บสามครั้งรวดในวินาทีเดียว แล้วนับว่าตัวเลขบนจอขึ้นกี่ใบ เทียบกับสามใบที่ส่งไปจริง

TESA Open Knowledge · © 2026 สมาคมสมองกลฝังตัวไทย (TESA) · ดัดแปลงจาก AIoT in Action (AIC มหาวิทยาลัยบูรพา) · CC BY-NC 4.0

05 คู่กับ 06 คือจุดขายของทั้งไลบรารี

สองไฟล์นี้ต่อกันแล้วได้วงจรที่ปิดครบ โต๊ะของเรา หมุนลูกบิด sensors.snapshot() หลอด LED ติด gpio.led(n).on() broker team03/telemetry team03/cmd สองหัวข้อ สองทิศทาง หน้าเว็บของทีม เห็นเลข knob ขยับ กดปุ่มส่งคำสั่ง {"cmd":"led","n":0} หกโมดูลทำงานพร้อมกันในเรื่องเดียว sensors gpio json mqtt wifi ui

ชุดบทเรียนก่อนหน้าเราเห็นบอร์ดอ่านของจริงและวาดขึ้นจอได้ วันนี้เราเห็นว่าของจริงตัวเดียวกันนั้นเดินทางออกไปได้ไกลแค่ไหน และเดินกลับมาสั่งงานได้ด้วย โดยที่โค้ดฝั่งเรายังเป็นภาษาไพทอนไม่กี่สิบบรรทัดเหมือนเดิม

ไม่มีบรรทัดไหนในสองไฟล์นี้ที่ต้องเขียนเรื่องคลื่นวิทยุ เรื่องโปรโตคอล หรือเรื่องการต่อ TCP เลย เพราะเฟิร์มแวร์ทำให้หมดแล้ว งานที่เหลือของเราคือ ตัดสินใจว่าจะส่งอะไรออกไป และจะยอมรับคำสั่งอะไรกลับมา

อีกสิบชุดบทเรียนข้างหน้าไม่มีอะไรใหญ่กว่าภาพนี้ มีแต่การทำให้มันปลอดภัยขึ้น เร็วขึ้น และฉลาดขึ้น — โครงยังเป็นภาพเดิม

TESA Open Knowledge · © 2026 สมาคมสมองกลฝังตัวไทย (TESA) · ดัดแปลงจาก AIoT in Action (AIC มหาวิทยาลัยบูรพา) · CC BY-NC 4.0

ไฟล์ 07 · 07_platform_in_one_call.py — อ่านนอกเวลา วันนี้ยังต่อไม่ได้

tesaiot.connect() TLS พอร์ต 8883 / 8884 broker.hivemq.com บอร์ดเข้าที่ 1883 ไม่เข้ารหัส คนละพอร์ต เข้ารหัสกับไม่เข้ารหัส วันนี้ไฟล์นี้อ่านคลังค่าตั้งอย่างเดียว ต่อจริงในบทเรียน 4.4–4.6 กับ 4.7–4.9
cfg = tesaiot.config()               # คลังค่าตั้งอยู่บนแฟลชของบอร์ด อ่านได้โดยไม่ต้องมีเน็ต
...
if PLATFORM_BROKER == "":            # วันนี้ยังว่าง: แสดงคลังค่าตั้งแล้วจบ ไม่เขียนแฟลช
    ...
    raise SystemExit
tesaiot.config_set("broker", PLATFORM_BROKER)   # รับข้อความทั้งสองอาร์กิวเมนต์
...
tesaiot.connect()                    # TLS พอร์ต 8883/8884 เสมอ ไม่อ่านคีย์ "port"
                                     # สั่งให้เริ่มต่อ แล้วคืนค่าทันที ไม่ได้รอให้ต่อเสร็จ
...
    if tesaiot.is_connected():       # จึงต้องวนถามเอง
        ...
    if waited >= WAIT_MS:            # และต้องมีกำหนดเวลาเลิกรอเสมอ
        break
...
    if tesaiot.publish(body):        # ไม่ต้องบอกหัวข้อ เฟิร์มแวร์ประกอบให้จาก device_id

ไฟล์นี้ต่อ broker.hivemq.com ไม่ได้ และไม่ได้ตั้งใจให้ต่อ tesaiot.connect() ต่อแบบเข้ารหัส TLS เสมอ ที่พอร์ต 8883 หรือ 8884 ตามโหมด และไม่อ่านคีย์ port ในคลังค่าตั้งเลย (mqtt_client_config.c:233-242 ในไลบรารีร่วม) ส่วน broker ของวันนี้รับบอร์ดที่ 1883 แบบไม่เข้ารหัส ไฟล์นี้จึงเป็นการอ่านนอกเวลาที่ชี้ไปบทเรียน 4.4–4.6 กับ 4.7–4.9 ถ้ารันวันนี้ มันแสดงคลังค่าตั้งแล้วจบ ไม่เขียนอะไรลงแฟลช

สิ่งที่มันสอนยังใช้ได้: ไฟล์ 05 กับ 06 ต้องพิมพ์ชื่อ broker ลงในโค้ดตรง ๆ ส่วนคลังค่าตั้งนี้ ตั้งครั้งเดียว เก็บบนแฟลชของบอร์ด ถอดไฟแล้วยังอยู่ โปรแกรมตัวถัดไปถาม config() เอาได้เลย

tesaiot.connect() ต่างจาก wifi.connect() ตรงที่มัน ไม่บล็อก ค่าที่คืนมาแปลว่า "รับคำสั่งไปแล้ว" ไม่ได้แปลว่า "ต่อได้แล้ว" ใครเขียน if tesaiot.connect(): แล้วส่งต่อทันทีจะได้ OSError เพราะสายยังไม่ขึ้น WAIT_MS คือกำหนดเวลาเลิกรอ ซึ่งแยกโปรแกรมที่ล้มเหลวอย่างสุภาพ ออกจากโปรแกรมที่ค้างจนคนดูต้องถอดไฟ

ตาคุณ อยู่ท้ายไฟล์ วันนี้จดค่า port กับ tls_mode ที่จอแสดง แล้วตอบว่าทำไม port ที่เห็นอาจไม่ใช่พอร์ตที่ connect() ใช้จริง · วันที่ได้ชื่อ broker ของแพลตฟอร์มในบทเรียน 4.4–4.6 ค่อยทำข้อถอดสาย USB แล้วดูว่าค่ายังอยู่ไหม

TESA Open Knowledge · © 2026 สมาคมสมองกลฝังตัวไทย (TESA) · ดัดแปลงจาก AIoT in Action (AIC มหาวิทยาลัยบูรพา) · CC BY-NC 4.0
เทปผลตรวจ 24 รอบ — แต่ละช่องคือหนึ่งรอบของลูป รันซ้ำกี่รอบก็ได้ผลเดิม สะดุดรอบเดียว หลุดจริง 4 รอบ ต่ออยู่ แต่ไม่มีเลข IP ช่วงสีส้มคือช่วงที่กฎตั้งต้นมองไม่เห็น รายงานได้ 1 จากที่ควรได้ 2
NEED = 3             # ต้องเสียติดกันกี่รอบถึงจะรายงาน
WANT_REPORTS = 2     # เทปชุดนี้มีปัญหาจริงอยู่สองช่วง
...
# ----- เติมส่วนนี้เอง (งานของคุณ) -----
def is_usable(online, ip):
    ...                # docstring ในไฟล์บอกว่า online กับ ip คืออะไร และใบ้วิธีเทียบสตริงไว้แล้ว
    return online      # ตอนนี้มันดูแค่ครึ่งเดียวของโจทย์
# ----- จบส่วนที่ต้องเติม -----

ไฟล์นี้ ไม่ต้องต่อเน็ตจริงและไม่ต้องไปปิดเราเตอร์ เพราะมันเล่นเทปผลตรวจลิงก์ที่บันทึกไว้แล้วจากบอร์ดจริง ระหว่างที่มีคนเดินถือมันออกนอกห้องแล้วเดินกลับมา รันซ้ำกี่รอบก็ได้ผลเดิม ซึ่งเป็นสิ่งที่การทดลองกับเน็ตจริงให้ไม่ได้ — กฎที่ทดสอบซ้ำไม่ได้ คือกฎที่ยังไม่รู้ว่าถูก

โจทย์คือลิงก์ที่ใช้ได้จริงต้องครบสองอย่างพร้อมกัน ต่ออยู่ และ มีเลข IP ที่ใช้ได้ — ซึ่งคือกับดัก "0.0.0.0" จากไฟล์ 01 ที่กลับมาอีกครั้ง คราวนี้มาในรูปของโจทย์ และหลังจากรันไฟล์ 05 มาแล้ว ทุกคนจะรู้ว่าทำไมมันสำคัญ เพราะลิงก์ที่ไม่มี IP คือลิงก์ที่ publish() ไปไม่ถึงไหน

จอบนบอร์ดคือเฉลย แก้แล้วรันใหม่จนเลขสองตัวตรงกัน ไม่ต้องรอผู้สอน · ตาคุณ อยู่ท้ายไฟล์ ลองเปลี่ยน NEED เป็น 5 แล้วดูว่าเราพลาดอะไรไป

TESA Open Knowledge · © 2026 สมาคมสมองกลฝังตัวไทย (TESA) · ดัดแปลงจาก AIoT in Action (AIC มหาวิทยาลัยบูรพา) · CC BY-NC 4.0

เจ็ดไฟล์เรียงแบบนี้เพราะอะไร

ความช่วยเหลือลดลงทีละขั้น จนขั้นสุดท้ายไม่มีเหลือ 01 · 04 · 02 เราทำให้ดู อ่านแล้วรันแล้วแก้ตามท้ายไฟล์ 05 · 06 ของจริงเต็มวง ส่งออกไป แล้วรับกลับมา 03 · 07 คุณตัดสินใจเอง เขียนกฎ และตั้งค่าที่บอร์ดจำ รูปแบบเดียวกับชุดบทเรียนก่อนหน้า แค่ชุดบทเรียนนี้ปลายทางไม่ได้จบอยู่บนจอบอร์ดแล้ว

ต้องทำในบทเรียน · เปิดตามลำดับนี้ ทั้งชุดราว 135 นาที โดยสไลด์แทรกอยู่ระหว่างการรันแต่ละไฟล์

ลำดับ · เวลา ไฟล์ ลงมือทำอะไร แล้วจะเข้าใจอะไร
1 · 25 นาที 01_wifi_first_connect.py ต่อเน็ตให้ได้ จับเวลาทั้งรหัสถูกและรหัสผิด · รู้ว่าจอที่นิ่งไม่ได้แปลว่าแฮงก์ และ "0.0.0.0" หลอกเราได้
2 · 15 นาที 04_scan_the_room.py ให้บอร์ดฟังคลื่นทั้งห้อง · แยก "ไม่ได้ยินวงนั้นเลย" ออกจาก "ได้ยินแต่รหัสผิด" ได้ด้วยตัวเอง
3 · 25 นาที 02_link_uptime.py ประกอบสี่โมดูลเป็นจอเฝ้าลิงก์หนึ่งใบ แล้วเดินถือบอร์ดออกไปให้มันหลุดจริง
4 · 25 นาที 05_value_leaves_the_board.py ส่งค่าลูกบิดจริงออกจากบอร์ด · เห็นบันไดสามขั้นและรู้ว่าตัวเองไปหยุดที่ขั้นไหน
5 · 25 นาที 06_command_comes_back.py รับคำสั่งจากที่ไกลมาสั่งไฟบนบอร์ด · เข้าใจว่ากล่องรับมีช่องเดียวแปลว่าอะไร
6 · 20 นาที 03_your_link_rule.py เขียนกฎ is_usable() เองจนเลขสองตัวบนจอตรงกัน · "ต่ออยู่" ไม่ได้แปลว่า "ส่งข้อมูลออกได้"
อ่านนอกเวลา 07_platform_in_one_call.py คลังค่าตั้งบนแฟลช · วันนี้อ่านอย่างเดียว tesaiot.connect() เป็น TLS เสมอ ต่อจริงบทเรียน 4.4–4.9

ทุกไฟล์จบด้วยบล็อก ตาคุณ — อ่านแล้วรันแล้วยังไม่จบ ต้องแก้แล้วรันซ้ำถึงจะจบ

TESA Open Knowledge · © 2026 สมาคมสมองกลฝังตัวไทย (TESA) · ดัดแปลงจาก AIoT in Action (AIC มหาวิทยาลัยบูรพา) · CC BY-NC 4.0

เรื่องที่เราให้ 70% ผู้เรียนเขียน 30%

30% ของชุดบทเรียนนี้ไม่ใช่การเขียนโค้ดคุมวิทยุ — มันไม่มี API แบบนั้นให้เรียก 70% · เฟิร์มแวร์ทำให้แล้ว 30% · งานของเรา คุมวิทยุ · จับมือกับเราเตอร์ · ขอเลข IP ต่อ TCP · พูดโปรโตคอล MQTT · ซิงก์นาฬิกา ส่งอะไรออกไป รับคำสั่งอะไร และนิยามว่า "ใช้ได้" แปลว่าอะไร

สิ่งที่เฟิร์มแวร์ทำให้แล้ว (70%)
คุมชิปวิทยุ · จับมือกับเราเตอร์ · ขอและต่ออายุเลข IP · ต่อ TCP ไปหา broker · พูดโปรโตคอล MQTT ทั้ง publish และ subscribe · ต่อ WiFi ใหม่เองเมื่อหลุด (ส่วน MQTT client ที่หลุดหรือถูกเตะไม่ต่อใหม่เอง) · ซิงก์นาฬิกาจากอินเทอร์เน็ต · วาดไอคอนบนแถบบนสุด

สิ่งที่เป็นงานของเรา (30%)
ตัดสินใจว่าจะส่ง ค่าอะไร ออกไป และในรูปแบบไหน · ยอมรับคำสั่งอะไรบ้าง และจะทำอย่างไรกับคำสั่งที่พิมพ์มาผิด · นิยามว่า "ลิงก์ใช้ได้" แปลว่าอะไรสำหรับงานของเรา · แล้วรายงานออกมาให้คนที่เดินผ่านหน้าจออ่านรู้เรื่องภายในสายตาเดียว

ภาพถ่ายหน้า Home ของบอร์ดหลังต่อ WiFi แถบบนมีนาฬิกาและไอคอน WiFi

ภาพถ่ายจอจริงของบอร์ด Eva Kit หน้า Home หลังต่อเน็ตสำเร็จ — บันทึกโดยผู้สอน · สองอย่างสุดท้ายในรายการ 70% อยู่ในภาพนี้ครบ คือนาฬิกา "Fri 14 Aug 15:39" กับไอคอน WiFi มุมขวาบน ทั้งคู่เฟิร์มแวร์วาดเอง ไม่มีโค้ดของเราสักบรรทัดไปสั่ง · ชุดบทเรียนก่อนหน้าสองช่องนั้นว่างเปล่า และเราบอกไว้ว่า "ชุดบทเรียนถัดไปต่อแล้วจะตรงเอง" — นี่คือหน้าตาของคำนั้นตอนเป็นจริง

สี่เรื่องนั้นไม่มีคำตอบสำเร็จรูปในเอกสารของผู้ผลิต เพราะมันขึ้นกับว่าเราจะเอาระบบนี้ไปทำอะไร

mqtt.publish() ใครก็เรียกได้ แต่การตอบว่า "ตกลงเราควรส่งอะไรออกไปให้คนอื่นเห็น" คือส่วนที่ไม่มีใครทำแทนเราได้

TESA Open Knowledge · © 2026 สมาคมสมองกลฝังตัวไทย (TESA) · ดัดแปลงจาก AIoT in Action (AIC มหาวิทยาลัยบูรพา) · CC BY-NC 4.0

วิธีรันบนบอร์ด

ภาพถ่ายหน้า BENTO Playground หลังต่อเน็ต แถบบนขวามีนาฬิกาและไอคอน WiFi สีเขียว

ภาพถ่ายจอจริงของบอร์ด Eva Kit หน้า BENTO Playground หลังต่อเน็ตสำเร็จ — แถบบนสุดขวาสุดมีนาฬิกา "Fri 14 Aug 11:04" กับไอคอน WiFi สีเขียวแล้ว ต่างจากชุดบทเรียนก่อนหน้าที่สองช่องนั้นว่างเปล่า · มุมขวาล่างคือปุ่มเปิดลิ้นชัก Console
  1. บนจอบอร์ด แตะการ์ด BENTO Playground เปิดค้างไว้ก่อนส่งโค้ดเสมอ
  2. บนคอม เปิด BENTO IDE เชื่อมต่อบอร์ด
  3. เปิดไฟล์จาก โฟลเดอร์ examples/ ของบทเรียน 1.4–1.6 แก้ WIFI_SSID กับ WIFI_PASS ให้ตรงกับ Hotspot มือถือของทีม และในไฟล์ 05 กับ 06 แก้ TEAM จาก teamXX เป็นเลขทีมที่ผู้สอนแจก ถ้าลืม ไฟล์จะหยุดตั้งแต่ขั้นแรกพร้อมบอกบนจอ เพราะชื่อทีมซ้ำจะเตะบอร์ดของทีมอื่นหลุด (BROKER ตั้งไว้แล้ว ไม่ต้องแก้) แล้วกด Program to Device
  4. หันไปมองจอบอร์ด แล้วรอ อย่ากดรีเซ็ตระหว่างที่จอนิ่ง
  5. แตะปุ่มไอคอนสีเขียวมุมขวาล่างเพื่อเปิดลิ้นชัก Console — ประวัติของทุกไฟล์อยู่ในนั้น
  6. ต่อสำเร็จแล้ว มองแถบบนสุด: ไอคอน WiFi ติดคือ "ต่อเครือข่ายได้" ส่วนนาฬิกาที่โผล่ตามมาคือ "ออกอินเทอร์เน็ตได้จริง" สองอย่างนี้ตอบคนละคำถาม

ภาพขยายแถบบนของจอบอร์ด: นาฬิกากับไอคอน WiFi สีเขียว

ภาพถ่ายจอจริงของบอร์ด Eva Kit เฉพาะแถบบนสุด ตัดมาจากภาพเดียวกันกับข้างบนเพื่อให้อ่านออก — บันทึกโดยผู้สอน · นี่คือสิ่งที่ข้อ 6 ให้มอง ขวาสุดคือนาฬิกากับไอคอน WiFi สีเขียว ซ้ายสุดคือบรรทัดลิขสิทธิ์ที่อยู่ตรงนั้นตลอดเวลา ไม่เกี่ยวกับสถานะเครือข่าย

พื้นที่วาดของเรายังเป็น 792 x 398 พิกเซล เท่าเดิม งบ widget ยังเป็น งบของคอร์ส 32 ตัว (เพดานเฟิร์มแวร์ 64) และมุมขวาล่างราว 100x58 ยังเป็นของปุ่ม Console ที่เฟิร์มแวร์จองไว้

ข้อควรระวังเรื่องเครือข่ายของห้อง: เครือข่ายที่ต้อง login หรือกดยอมรับเงื่อนไขบนหน้าเว็บก่อน เช่น WiFi ขององค์กร จะทำให้ connect() คืน True และได้เลข IP ตามปกติ แต่นาฬิกาไม่ขึ้น และ mqtt.connect() จะไปไม่ถึง broker — นั่นคือกรณี "ติดข้อแรก ไม่ผ่านข้อสอง" · เน็ตที่กันพอร์ต 1883 ขาออกก็ไปไม่ถึงเช่นกัน

TESA Open Knowledge · © 2026 สมาคมสมองกลฝังตัวไทย (TESA) · ดัดแปลงจาก AIoT in Action (AIC มหาวิทยาลัยบูรพา) · CC BY-NC 4.0

ข้อมูลไหลไปทางไหน — วิทยุอยู่ฝั่งเดียวกับโค้ดเรา

ผลของ wifi.* และ mqtt.* เดินทางไปขึ้นจอด้วยเส้นทาง IPC เส้นเดิมจากชุดบทเรียนก่อนหน้า CM33 โค้ด Python ของเรา และตัวคุมวิทยุ ชิปวิทยุ Eva: CYW55513IUBG WiFi + BT ใช้เสาร่วมกัน เราเตอร์ แจกเลข IP broker พอร์ต 1883 CM55 วาดจอ ไม่ยุ่งกับเน็ต เน็ตทั้งเส้นอยู่ฝั่ง CM33 คอร์ที่รันโค้ดของเรา จึงบล็อกได้ทั้งโปรแกรม และจึงต้องเคาะ ui.poll() ให้จอทันก่อนเข้าบรรทัดนั้น

ชุดบทเรียนก่อนหน้าเราเห็นแล้วว่า CM33 รันโค้ดของเรา ส่วน CM55 วาดจอ วันนี้มีข้อเท็จจริงเพิ่มมาข้อเดียว: ทั้งวิทยุ WiFi และงาน MQTT อยู่ฝั่ง CM33 เหมือนกัน

นั่นอธิบายทุกอย่างที่เราเจอวันนี้ — wifi.connect() บล็อกได้ทั้งโปรแกรม เพราะมันบล็อกคอร์เดียวกับที่รัน Python ของเรา และระหว่างที่มันบล็อก ไม่มีใครเหลือไปเคาะกล่องจดหมายให้ CM55 วาดอะไรใหม่ ส่วน mqtt.get_message() ตรงกันข้าม มันไม่บล็อกเลย จึงต้องเป็นเราที่วนถามเอง

ถ้าเข้าใจสไลด์นี้ จะไม่มีวันเขียนป้ายบอกสถานะไว้หลังบรรทัดที่บล็อกอีกเลย

TESA Open Knowledge · © 2026 สมาคมสมองกลฝังตัวไทย (TESA) · ดัดแปลงจาก AIoT in Action (AIC มหาวิทยาลัยบูรพา) · CC BY-NC 4.0

ผู้สอน: ครึ่งหนึ่งของโมดูลนี้เป็นเรื่องชิปนิรภัย OPTIGA — ทั้งสองบอร์ดคอมไพล์โมดูล `optiga` มาเหมือนกัน ที่ต่างคือสวิตช์บิลด์ `ENABLE_OPTIGA_CLM` ฝั่ง CM33 ซึ่ง Dev Kit เปิดเป็นค่าปริยาย ส่วน Eva Kit ปิดไว้ และ Makefile ของ Eva เขียนกำกับว่ายังไม่ได้พิสูจน์บนฮาร์ดแวร์ · ส่วน `ENABLE_OPTIGA ?= 0` ที่หัวไฟล์ 07 พูดถึงเป็นอีกสวิตช์หนึ่งของฝั่ง CM55 ซึ่งปิดไว้ทั้งสองบอร์ด ไฟล์นี้จึงไม่แตะฝั่งนั้นเลยสักตัวไม่ว่าบอร์ดไหน เรื่องความปลอดภัยของจริงเป็นงานของบทเรียน 4.7–4.9

ทีมละหนึ่งบอร์ด สลับกันเป็นคนพิมพ์ทุกช่วง คนที่ไม่ได้พิมพ์รับหน้าที่จับเวลาและจดตัวเลข