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

บทเรียน 4.8 — โมดูล tesaiot: MQTTs สู่แพลตฟอร์ม

MQTTs (serverTLS) · จากพอร์ต 1883 ที่ใครก็อ่านได้ ไปพอร์ต 8884 ที่รู้ว่ากำลังคุยกับใคร

โมดูล 4 — เชื่อมต่อแพลตฟอร์ม IoT

ต่อจากบทเรียน 4.7 — TLS: ใบรับรอง ห่วงโซ่ความเชื่อถือ และการจับมือ

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

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

70% — เฟิร์มแวร์ + แพลตฟอร์มทำให้แล้ว TLS · ใบรับรอง · root CA · broker · ฐานข้อมูล · กราฟ 30% — งานของเรา ตัวตน · การรอ · หลักฐาน โค้ดวันนี้สั้นกว่าบทเรียน 4.4–4.6 แต่ตอบผิดหนึ่งข้อแล้วต่อไม่ติดทั้งบทเรียน งานที่เหลือให้เราไม่ใช่การพิมพ์ แต่คือการรู้ว่าอะไรพิสูจน์อะไร

สิ่งที่ทำให้แล้ว (70%)
การจับมือ TLS ทั้งหมด · การตรวจใบรับรองย้อนขึ้นไปถึง root · root CA ที่ฝังมากับเฟิร์มแวร์ · การเลือกพอร์ตจาก tls_mode · การประกอบ topic จาก device_id · ฝั่งแพลตฟอร์ม: broker EMQX, บริดจ์ที่รอ subscribe อยู่แล้ว, ฐานข้อมูลอนุกรมเวลา และกราฟที่สร้างจากชื่อคีย์ JSON อัตโนมัติ

สิ่งที่เป็นงานของเรา (30%)
ตั้งค่าตัวตนของอุปกรณ์ให้ถูกทั้งสี่ค่า · รอให้การเชื่อมต่อเสร็จจริงก่อนส่ง · เลือกว่าจะส่งฟิลด์อะไรเป็นตัวเลข · และแสดงหลักฐานบนจอให้คนอื่นตรวจได้โดยไม่ต้องเปิดโค้ด

ยิ่งไลบรารีทำให้เยอะ ความผิดพลาดที่เหลือยิ่ง เงียบ — เพราะสิ่งที่เหลือให้เราพลาดคือเรื่องที่ไลบรารีไม่มีทางรู้ว่าเราตั้งใจอะไร

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

import tesaiot ได้มา 28 ชื่อ — ใช้ได้จริงเก้าตัว ทั้ง Eva Kit และ Dev Kit

เก้าตัวที่ทำงาน ทั้งหมดอยู่บน CM33 ไม่ข้ามคอร์ config() config_set() config_reset() config_reload() connect() disconnect() is_connected() publish() slots() ทั้งชุดบทเรียนนี้ใช้แค่กล่องนี้ สิบหกตัวที่ข้ามคอร์ไปหาชิป ส่ง IPC ไป CM55 ที่ประกอบมาโดยไม่มี OPTIGA init() device_id() health() license_verify() sign() cred_read/write/erase() random() hash() hmac() aes_keygen() encrypt() decrypt() counter_read() counter_inc() ยังไม่ได้วัดเวลาจริง — อย่าเรียกในลูป อีกสามตัว คนละเรื่อง protected_update() ห้ามเรียกในชุดบทเรียนนี้ Eva: ปิดไว้ (OSError) Dev Kit: เขียนลงชิปจริง http_post() HTTPS จาก CM33 ตาม tls_mode device_identity() ตัวตนบอร์ดให้หน้า Add Device

พูดให้ตรง: ทั้งสองบอร์ดประกอบเฟิร์มแวร์ของคอร์จอด้วย ENABLE_OPTIGA ?= 0 สิบหกคำสั่งที่ข้ามคอร์จึงไม่มีชิปให้คุย · ซอร์สบอกว่าคอร์จอตอบ "ไม่มีให้" กลับมาโดยไม่รอชิป แล้วฝั่ง Python โยน OSError พร้อมข้อความ (เพดานรอ 10 วินาทีมีไว้กรณีคอร์จอไม่ตอบเลย) — ยังไม่ได้วัดเวลาจริงบนบอร์ดไหน ลอง t=time.ticks_ms(); tesaiot.health(); print(time.ticks_diff(time.ticks_ms(), t)) บนโต๊ะก่อนสอน แล้วบอกผู้เรียนด้วยตัวเลขที่วัดได้

เรื่องเดียวที่สองบอร์ดต่างกันจริง คือ tesaiot.protected_update() — บน Eva ไม่ได้ถูกคอมไพล์เข้ามา (OSError) แต่บน Dev Kit (ENABLE_OPTIGA_CLM=1) มันทำงานจริง: ขอชุด Protected Update จากแพลตฟอร์มแล้วเขียนใบรับรองลงช่อง E0E1 ของชิป OPTIGA และ csr=True สร้างคู่กุญแจใหม่ทับของเดิม — ห้ามเรียกในชุดบทเรียนนี้ ทั้งจากไฟล์ตัวอย่างและ REPL เพราะสิ่งที่เขียนลงชิปย้อนกลับเองไม่ได้

ชิป OPTIGA มีอยู่จริงและใช้ได้ ผ่านโมดูล optiga (สไลด์โบนัสท้ายบทเรียน) — Eva: I2C จาก CM33 ตรง ๆ · Dev Kit: ชิปอยู่บนบัสจอของ CM55 จอหยุดรับสัมผัสชั่วครู่ทุกครั้งที่เรียก และยังไม่ได้ตรวจว่าทุกบอร์ดมีชิปครบ — ลอง optiga.uid() ก่อน

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

เก้าตัวที่ใช้ได้ — ตารางเต็มพร้อมกับดักของแต่ละตัว

เรียกอย่างไร คืนอะไร สิ่งที่ต้องรู้
tesaiot.config() dict 19 คีย์ คีย์ครบชุด: tls_mode device_id factory_uid api_key broker port sni_hostname qos keepalive timeout_ms max_retries retry_interval_ms api_host api_port api_endpoint wifi_ssid sntp_server sntp_timezone debug_level
tesaiot.config_set(key, value) True / False รับ สตริงทั้งสองช่อง ตัวเลขก็ต้องส่งเป็นสตริง · คีย์ผิดคืน False เงียบ ๆ ต้องรับค่ากลับมาดู · ตั้ง "tls_mode","server_tls" แล้วอ่านกลับได้ "serverTLS" เพราะมันแปลงชื่อให้
tesaiot.config_reset() None ล้างกลับเป็นค่าโรงงาน ทั้ง 19 คีย์ ตัวตนของอุปกรณ์หายหมด ต้องตั้งใหม่ทุกค่า
tesaiot.config_reload() True / False อ่านไฟล์ตั้งค่าจากแฟลชขึ้นมาใหม่ ทับค่าที่แก้ไว้ในหน่วยความจำ · ใช้ทิ้งการแก้ที่ยังไม่พอใจ (ข้อควรรู้: ในซอร์ส tesaiot_config_store.c ปัจจุบัน config_set() เซฟลงแฟลชทุกครั้ง reload จึงอาจย้อนค่าที่ตั้งด้วย config_set() ไม่ได้ — ต้องยืนยันบนบอร์ด)
tesaiot.connect() True / False True แปลว่า งานเริ่มแล้ว ไม่ใช่ต่อเสร็จแล้ว ต้องวนรอ is_connected() เอง
tesaiot.disconnect() True / False ตัวนี้คืน bool ไม่เหมือน mqtt.disconnect() ที่คืน None — สองโมดูลไม่เหมือนกัน
tesaiot.is_connected() True / False ตัวจริงที่ตอบว่าต่อเสร็จหรือยัง
tesaiot.publish(payload, topic=None) True / False payload มาก่อน topic และ topic ใส่หรือไม่ใส่ก็ได้ ไม่ใส่แล้วเฟิร์มแวร์ประกอบให้จาก device_id
tesaiot.slots() dict 13 คู่ ตอบได้ทันทีโดยไม่แตะชิป เพราะเป็นตารางชื่อในซอร์ส · ช่อง 4 ถูกกันไว้ จึงไม่อยู่ในรายการ

tesaiot.publish() กับ mqtt.publish() สลับลำดับกัน — ชุดบทเรียนก่อนหน้าเขียน mqtt.publish(topic, payload) วันนี้เขียน tesaiot.publish(payload) สลับเมื่อไรได้ผลประหลาดทันทีโดยไม่มี error เพราะทั้งสองช่องรับสตริงเหมือนกัน

config_set() เก็บค่าไว้เฉย ๆ ยังไม่ได้ต่ออะไรทั้งนั้น พิมพ์คีย์ผิดจะไม่มีใครเตือนจนกว่าจะต่อไม่ติด — จึงต้อง print(tesaiot.config()) หนึ่งครั้งหลังตั้งค่าเสมอ

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

แกะโค้ดจริง — ท่าที่ 1 ตั้งค่าตัวตนของอุปกรณ์

# --- ท่าที่ 1: ตั้งค่าตัวตนของอุปกรณ์ ---
tesaiot.config_set("device_id", DEVICE_ID)
tesaiot.config_set("api_key", API_KEY)
tesaiot.config_set("mqtt_pass", MQTT_PASS)
tesaiot.config_set("broker", BROKER)
tesaiot.config_set("sni_hostname", BROKER)     # ต้องเป็นชื่อเดียวกับ broker
print("config ปัจจุบัน:", tesaiot.config())

config_set() รับ ทีละคู่ (key, value) และแค่ เก็บค่าไว้ในเฟิร์มแวร์ ยังไม่ได้ต่ออะไรทั้งสิ้น — พิมพ์ชื่อคีย์ผิดก็ไม่มีใครเตือน ค่าที่ผิดจะไปโผล่ตอนต่อไม่ติดเท่านั้น

จึงต้อง print(tesaiot.config()) ทุกครั้งหลังตั้งค่า ก่อน จะไปท่าถัดไป — เป็นวิธีเดียวที่เห็นว่าค่าเข้าครบและสะกดถูก

device_id ต้อง สั้นกว่า 31 ตัวอักษร เกินแล้วถูกตัดเงียบ แล้วแพลตฟอร์มจะหาอุปกรณ์ไม่เจอ

ตัวตน = สามอย่างรวมกัน device_id — ชื่อที่แพลตฟอร์มรู้จัก api_key — กุญแจของ API mqtt_pass — รหัสของ broker ขาดข้อใดข้อหนึ่ง = ถูกปฏิเสธ

sni_hostname ไม่ใช่ค่าเสริม — มันคือชื่อที่ทำให้เซิร์ฟเวอร์ หยิบใบรับรองใบที่ถูกมายื่นให้เรา ตั้งไม่ตรงเมื่อไร การจับมือล้มโดยไม่มีข้อความบอก

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

แกะโค้ดจริง — ท่าที่ 2 สั่งต่อ แล้ววนรอจนต่อเสร็จจริง

# --- connect() เป็น API แบบ asynchronous ---
tesaiot.connect()                       # คืนค่าทันที ยังไม่ได้แปลว่าต่อแล้ว
t0 = time.ticks_ms()
...
while not tesaiot.is_connected():       # ตัวจริงที่ตอบได้คือ is_connected()
    waited = time.ticks_diff(time.ticks_ms(), t0)
    if waited > WAIT_CEILING_S * 1000:  # WAIT_CEILING_S = 30 ตัวเลขเดียวกับบนจอ
        print("ต่อแพลตฟอร์มไม่สำเร็จใน 30 วินาที")
        break
    ...
    time.sleep_ms(500)
connect() คืนค่าตรงนี้ TCP จับมือ TLS จับมือ + ตรวจใบรับรอง MQTT CONNECT is_connected() เป็น True ตรงนี้ หลายวินาทีหลังจากนั้น publish() ที่เขียนต่อท้าย connect() ทันที จะยิงลงช่วงกลางเส้นนี้ แล้วหายไปเงียบ ๆ ลูปรอทุกลูปต้องมี timeout — ไม่งั้นวันที่เน็ตล่ม โปรแกรมค้างตรงนี้ตลอดกาลโดยไม่มีใครรู้

tesaiot.connect() เป็น API แบบ asynchronous คืนค่าทันทีเพื่อไม่บล็อกโปรแกรม — ค่าที่คืนมา ไม่ใช่สถานะสุดท้าย สิ่งเดียวที่ตอบได้ว่าต่อเสร็จหรือยังคือ tesaiot.is_connected() · โจทย์ต่อ: ให้ลบลูปนี้ออกแล้วรันหนึ่งรอบ — จะเห็นด้วยตาว่า publish ที่ยิงเร็วเกินไป ไม่ error และไม่ถึงแพลตฟอร์ม

"ฟังก์ชันคืนค่าแล้ว" กับ "งานเสร็จแล้ว" เป็นคนละเรื่องเสมอสำหรับ API แบบ async — ความต่างนั้นวัดได้เป็นวินาที

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

แกะโค้ดจริง — ท่าที่ 3 และ 4 ส่งค่าจริง แล้วโชว์หลักฐานบนจอ

m = sensors.bmi270.motion()                       # (ax, ay, az, gx, gy, gz)
payload = {"accel_x": round(m[0], 2),
           "heading": round(sensors.bmm350.heading(), 1),
           "pot": sensors.pot.percent()}
tesaiot.publish(json.dumps(payload))              # ไม่ต้องใส่ topic เฟิร์มแวร์ประกอบให้
cfg = tesaiot.config()
lcd.print("ส่งครั้งที่", sent, "| โหมด", cfg["tls_mode"], "-> 8884")
ค่าจากเซนเซอร์ ax · heading · pot round() ก่อนเสมอ ตัวเลขจริง ไม่ใช่สตริง ส่งแบน ไม่ต้องห่อ {"accel_x":0.12, "heading":183.4} แพลตฟอร์มห่อให้เอง ชื่อคีย์ตรงได้หน่วย เข้ารหัสแล้วส่ง พอร์ต 8884 topic ประกอบจาก device_id เราไม่ต้องพิมพ์ topic เอง จอบอร์ด ตัวนับเดินขึ้น tls_mode ที่ใช้จริง ท่าที่ 4 ไม่ใช่ของประดับ — มันคือหลักฐานที่กรรมการอ่านได้โดยไม่ต้องเปิดโค้ด

tesaiot.publish(payload) รับ ตัวข้อมูลอย่างเดียว — ต่างจาก mqtt.publish(topic, payload) ของชุดบทเรียนที่แล้ว เพราะเฟิร์มแวร์ประกอบ topic ให้เองจาก device_id ที่เราตั้งไว้ในท่าที่ 1 (จะระบุ topic เองก็ได้ แต่ชุดบทเรียนนี้ไม่ต้อง) · ส่ง ตัวเลขจริง ไม่ใช่สตริง ไม่งั้น dashboard จะขึ้นค่าแต่วาดกราฟไม่ได้ — กับดักเดียวกับบทเรียน 4.4–4.6

จอบอร์ดต้องตอบคำถาม MVP ได้เอง: ทีมไหน · โหมดอะไร · ส่งไปกี่ครั้งแล้ว สามอย่างนี้ทำให้คนอื่นตรวจงานเราได้โดยไม่ต้องถาม

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

ข้อมูลไหลไปทางไหน — ตั้งแต่เซนเซอร์ถึงกราฟ

เซนเซอร์ BMI270 · pot โค้ดของเรา json.dumps ชั้น TLS เข้ารหัสทุกไบต์ EMQX listener 8884 บริดจ์ + ฐานข้อมูล อนุกรมเวลา กราฟ อัปเดตสด กล่องสีม่วงคือทั้งหมดที่เพิ่มมาจากบทเรียน 4.4–4.6 — ที่เหลือเหมือนเดิมทุกกล่อง และมันอยู่ในเฟิร์มแวร์ ไม่ได้อยู่ในโค้ดที่เราเขียน ดีบักตามลำดับนี้เสมอ: is_connected() → print(config()) → กราฟบนแพลตฟอร์ม ถ้า is_connected() ยัง False ปัญหายังไม่เดินทางไปถึงเรื่อง topic หรือรูปร่าง JSON เลย อย่าเริ่มเดาจากปลายทาง — กราฟว่างเปล่าบอกได้แค่ว่า "ไม่ถึง" ไม่ได้บอกว่าตกตรงไหน

ระบบยาวหกกล่อง แต่การดีบักไม่เคยยาวกว่า "หาให้เจอว่ากล่องสุดท้ายที่ยังเห็นข้อมูลคือกล่องไหน" — ชุดบทเรียนนี้กล่องแรกที่ต้องตรวจคือ is_connected()

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

อีกสี่ตัวที่โครงหลักไม่ได้เรียก — แต่ต้องเคยลอง

# --- ล้างแล้วตั้งใหม่: ทางออกเมื่อตั้งค่ามั่วจนไม่รู้ว่าเหลืออะไรอยู่ ---
tesaiot.config_reset()            # คืน None และล้างครบทั้ง 19 คีย์
tesaiot.config_set("device_id", DEVICE_ID)   # ต้องตั้งใหม่ทุกค่า

# --- ทิ้งการแก้ที่ยังไม่พอใจ แล้วดึงของเดิมจากแฟลชกลับมา ---
tesaiot.config_reload()           # True ถ้าอ่านไฟล์ตั้งค่าสำเร็จ

# --- ชื่อช่องเก็บความลับ ตอบได้โดยไม่ต้องแตะชิป ---
print(tesaiot.slots())            # {'device_id': 0, 'license': 1, ...}

# --- ปิดงานให้เรียบร้อย ตัวนี้คืน bool ไม่เหมือน mqtt.disconnect() ---
tesaiot.disconnect()
config_reset() ล้างครบทั้ง 19 คีย์ ตัวตนของอุปกรณ์หายด้วย ต้องตั้งใหม่ทุกค่าก่อนต่อ ใช้ตอนหลงทางเท่านั้น config_reload() อ่านไฟล์จากแฟลชกลับมา ทับค่าที่แก้ค้างไว้ ของที่ยังไม่ได้เซฟหายไป ใช้ตอนอยากย้อนกลับ slots() คืนชื่อช่องเก็บความลับ 13 ช่อง ตอบทันที ไม่แตะชิป ไม่ค้าง ช่อง 4 ถูกกันไว้ ไม่อยู่ในนี้ แผนที่ของงานบทเรียนต่อ ๆ ไป

config_reset() ล้างของจริง ส่วน config_reload() แค่ย้อนกลับไปหาของที่เซฟไว้ — ตั้งค่ามั่วจนงงว่าเหลืออะไรอยู่ ให้ config_reset() แล้วเริ่มใหม่จากศูนย์ ดีกว่าไล่แก้ทีละคีย์ · slots() ตอบได้โดยไม่ต้องข้ามคอร์ไปถามชิป เพราะอ่านตารางชื่อในเฟิร์มแวร์ — แผนที่ว่าถ้าวันหนึ่งเปิด OPTIGA ได้ ความลับแต่ละอย่างจะไปนอนช่องไหน · tesaiot.disconnect() คืน True/False ไม่เหมือน mqtt.disconnect() ที่คืน None อย่าจำรวมกัน

01_config_store.py ไล่คีย์ทั้ง 19 · 02_config_reset_reload.py สองตัวที่สลับกันง่าย · 03_slots_and_the_dead_half.py จับเวลาครึ่งที่ต้องมีชิป · 04_disconnect_and_republish.py ปิดแล้วเปิดใหม่

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

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

1 · ก่อนแตะโค้ด จดค่าประจำตัวสี่ตัวจากแพลตฟอร์ม ลงบันทึกการเรียน ต่อ WiFi ให้ได้ก่อนเสมอ นับตัวอักษร device_id ให้ไม่เกิน 31 2 · แก้ห้าบรรทัดบนหัวไฟล์ TEAM_NAME · DEVICE_ID API_KEY · MQTT_PASS · BROKER ที่เหลือเหมือนกันทุกบอร์ด เติมช่องว่างทีละจุด แล้วรันทุกครั้ง 3 · รันแล้วดูสองจอ จอบอร์ด: ตัวนับกับ tls_mode จอคอม: dashboard ของแพลตฟอร์ม ขยับบอร์ดแล้วกราฟต้องขยับตาม
  1. เปิดหน้า BENTO Playground บนบอร์ดค้างไว้ แล้วต่อ WiFi ให้เรียบร้อยก่อน (wifi.connect() ของบทเรียน 4.1–4.3 บล็อกได้นานถึงราว 85 วินาทีถ้ารหัสผิด)
  2. เปิด s11_secure_telemetry.py แก้ห้าบรรทัดบนหัวไฟล์ให้เป็นค่าของอุปกรณ์คุณ
  3. เติมช่องว่างท่าที่ 1 ให้ครบ กด Program to Device แล้วดูว่า print(tesaiot.config()) ขึ้นค่าครบและสะกดถูก ก่อน ไปท่าที่ 2
  4. เติมท่าที่ 2 แล้วรัน — จับเวลาว่ากี่วินาที is_connected() จึงเป็น True แล้วจดลงบันทึกการเรียน
  5. เติมท่าที่ 3 และ 4 แล้วเปิด dashboard ของแพลตฟอร์ม เลือกอุปกรณ์ของคุณ ดูกราฟขยับพร้อมกับตัวนับบนจอบอร์ด
  6. ถ่ายภาพทั้งสองจอเก็บไว้เป็นหลักฐาน แล้วค่อยไปทำตารางเทียบ 1883 กับ 8884 ในบันทึกการเรียน

ตัวอย่างของบทเรียน 4.7–4.9 — สามไฟล์แรกคือชุดที่ย้ายลูปส่งข้อมูลขึ้นช่องที่เข้ารหัส

ต้องทำในบทเรียน · เปิดตามลำดับนี้ ทั้งชุดราว 29 นาที

ลำดับ · เรื่อง · เวลา ไฟล์ ลงมือทำอะไร แล้วจะเข้าใจอะไร
1 · อ่านค่าที่บอร์ดเก็บไว้ก่อนต่ออะไร · 6 นาที 01_config_store.py กรอกกล่องค่าประจำตัวในบันทึกการเรียน จากค่าที่บอร์ดตอบจริง ไม่ใช่จากค่าที่จดมา · จะเข้าใจว่าต้องอ่านค่าที่บอร์ดเก็บไว้ให้ครบก่อน แล้วค่อยสั่งต่ออะไร
2 · รอให้ต่อเสร็จเป็น · 8 นาที 05_wait_for_connected.py เขียนลูปรอด้วย is_connected() และจับเวลาจริงลงบันทึกการเรียน · จะเข้าใจว่า connect() คืนค่าก่อนต่อเสร็จ จึงต้องรอเป็น ไม่ใช่เชื่อว่าคืนค่าแล้วคือพร้อม
3 · ลูปส่ง telemetry บนช่องที่เข้ารหัส · 15 นาที 06_secure_publish_loop.py ประกอบ MVP ของชุดบทเรียนนี้ — ตัวนับเดินขึ้นบนจอ พร้อมกราฟที่ขยับบน dashboard · จะเห็นว่าลูปส่ง telemetry ตัวเดิมย้ายขึ้นช่องที่เข้ารหัสได้ โดยรูปร่างของลูปไม่เปลี่ยน

ทั้งสามไฟล์ ยังรันไม่ได้จนกว่าบอร์ดจะมี device_id ของตัวเอง (จากบัญชี TESAIoT Platform ของคุณ) นี่ไม่ใช่ข้อบกพร่องของไฟล์ แต่เป็นงานเตรียมการที่ต้องเสร็จก่อนบทเรียน ถ้ายังไม่ได้ค่าประจำตัว ให้อ่านโครงไปก่อนแล้วรันเมื่อได้ค่ามา

ติดตรงไหน เปิดอันนี้

อาการที่เจอ ไฟล์ที่ตอบอาการนั้น
ต่อไม่ติด และแยกไม่ออกว่าติดที่ WiFi หรือที่แพลตฟอร์ม 04_ping_two_targets.py — พิสูจน์ให้จบก่อนว่าออกอินเทอร์เน็ตได้จริง แล้วค่อยโทษ TLS
ตอบไม่ได้ว่าวันนี้ต่างจากบทเรียน 4.4–4.6 ตรงไหน 03_connect_and_publish.py — เปิดคู่กับไฟล์ที่ 3 ข้างบน แล้วเทียบทีละบรรทัดว่าอะไรเปลี่ยน

อ่านเสริมนอกเวลา — เรื่องนี้อยู่นอกเกณฑ์ผ่านของบทเรียน 4.7–4.9 แต่เป็นนิสัยที่ใช้ได้ทั้งบทเรียน 4.4–4.6 และชุดบทเรียนนี้: 06_sent_is_not_delivered.py สอนให้นับใบที่ ถึงปลายทาง ไม่ใช่นับใบที่เราสั่งส่ง · ส่วนตัวอย่างที่เทียบ 1883 กับ 8884 ให้เห็นด้วยตาว่า TLS ปกป้องอะไร ยังไม่มีในคลัง วันนี้จึงใช้ตารางเทียบในบันทึกการเรียน กับผลดักฟังในสไลด์แทน

ก่อนหน้านี้ชุดบทเรียนนี้มีตัวอย่างอีกสามไฟล์ที่บันทึกพฤติกรรมแปลกของ tesaiot.config() ไว้ (ตั้งค่าแล้วอ่านกลับได้อีกคำ · พิมพ์ผิดแล้วยังคืน True · คีย์ port ที่ไม่มีใครอ่าน) ทั้งสามถูกถอดออก เพราะเป็นรายงานบั๊กของเฟิร์มแวร์ ไม่ใช่นิสัยที่เอาไปใช้กับงานตัวเองได้ ความจริงเรื่องพอร์ตยังอยู่ในสไลด์ "พอร์ตมาจาก tls_mode ไม่ใช่คีย์ port" และในตารางกับดัก

อย่าเติมครบทั้งห้าจุดแล้วค่อยรัน — บนเส้นทางที่มี TLS อยู่ตรงกลาง จุดที่พังได้มีมากกว่าชุดบทเรียนก่อนหน้า และไม่มีจุดไหนส่งเสียงเลย

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

ที่ต้องบอกกันไว้ก่อน เพราะถ้าไม่บอก ผู้เรียนจะไปเจอเองตอนนั่งดีบักแล้วเข้าใจว่าตัวเองเขียนผิด ทั้งที่ไม่ได้ผิดเลย · แหล่งที่ดู: ipc_service.c handle_tesaiot() ตอบ status 0xFD เมื่อไม่มี ENABLE_OPTIGA · modtesaiot.c tesaiot_ipc_send() รอสูงสุด TESAIOT_IPC_RESPONSE_TIMEOUT_MS = 10000 เฉพาะเมื่อไม่มีคำตอบ และ wrapper แต่ละตัว mp_raise_msg(OSError, "... failed")