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

บทเรียน 4.9 — ลงมือทำ: ส่งค่าจริงผ่านช่องทางเข้ารหัส

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

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

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

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

MVP checkpoint — ผ่านชุดบทเรียนนี้เมื่อ

หน้าจอ Dashboard ของ TESAIoT Community Edition ที่ใช้ดูกราฟของอุปกรณ์

ภาพหน้าจอ: TESAIoT Community Edition v1.1.8 — เอกสารของ repo (Apache-2.0) — หน้าตาของ dashboard ที่จะไปดูกราฟของทีม

telemetry ของทีมขึ้น dashboard แพลตฟอร์มผ่าน TLS + ตอบได้ว่าต่างจากบทเรียน 4.4–4.6 ตรงไหน

  • [ ] กราฟของ device_id ทีมนั้น ขยับ บน dashboard ของแพลตฟอร์มจริง และขยับตามเมื่อเอียงบอร์ด
  • [ ] จอบอร์ดแสดง device_id + โหมด tls_mode + ตัวนับที่เดินขึ้นต่อเนื่อง
  • [ ] กล่องค่าประจำตัวในบันทึกการเรียน ครบสี่ค่า และ device_id ไม่ซ้ำทีมอื่น
  • [ ] ตารางเทียบ 1883 กับ 8884 ในบันทึกการเรียน กรอกครบทั้งเจ็ดแถว จากสิ่งที่เห็นเอง ไม่ใช่ลอกสไลด์
  • [ ] ตอบได้โดยไม่เปิดสไลด์ว่า serverTLS ปกป้องอะไร และไม่ปกป้องอะไร
  • [ ] ตอบได้ว่าพอร์ต 8884 ถูกเลือกมาจากอะไร (และทำไมไม่ใช่จากคีย์ port)

ข้อที่ห้าคือหัวใจ — ถ้าตอบไม่ได้ เราติดตั้งความปลอดภัยเป็น แต่ยังไม่รู้ว่าเราซื้ออะไรมาด้วยราคาเท่าไร

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

กับดักที่เจอบ่อย

อาการ สาเหตุที่แท้จริง วิธีแก้
ต่อไม่ติด ไม่มีข้อความอะไรเลย sni_hostname ไม่ตรงกับ broker เซิร์ฟเวอร์ยื่นใบผิดใบ ตั้งสองค่านี้เป็นสตริงเดียวกันเสมอ
ต่อไม่ติดกับ CE ที่ติดตั้งเอง CE สุ่ม root CA ใหม่ทุกการติดตั้ง บอร์ดฝัง CA ของแพลตฟอร์มไว้ ใช้โฮสต์ของแพลตฟอร์ม TESAIoT ที่ได้มาพร้อมตัวตนของอุปกรณ์ — MQTTs เข้า CE ต้อง build เฟิร์มแวร์ใหม่
ตั้ง port แล้วพอร์ตไม่เปลี่ยน พอร์ตมาจาก tls_mode คีย์ port เปลี่ยนแค่ป้ายที่แสดง ดู tls_mode เป็นคำตอบ อย่าดูคีย์ port
publish() ไม่ error แต่ไม่มีข้อมูลบนแพลตฟอร์ม ยิงก่อน is_connected() เป็น True เพราะ connect() เป็น async วนรอด้วย while not tesaiot.is_connected() พร้อม timeout
โปรแกรมค้างตรงลูปรอ ไม่ไปไหนเลย ลูปรอไม่มี timeout วันที่เน็ตล่มจึงวนตลอดกาล time.ticks_diff() เกิน 30000 ms แล้ว break
หลายบอร์ดหลุดสลับกันเป็นจังหวะ หลายบอร์ดใช้ device_id ค่าเริ่มต้นตัวเดียวกัน broker เตะตัวเก่าออก หนึ่งบอร์ดหนึ่ง device_id ที่ provision มา
ข้อมูลขึ้นแต่ไม่มีเส้นกราฟ ส่งค่าเป็นสตริง ส่งตัวเลขจริง ไม่ใช่ "25.5"
ชื่อวัดขึ้นต้นด้วย data_ และไม่มีหน่วย ห่อ payload เองด้วย {"data": ...} ส่งแบน bridge ห่อให้เองอยู่แล้ว

แปดแถวนี้เป็นเรื่องสาย ใบรับรอง และรูปร่างข้อมูล — หน้าถัดไปคือกับดักที่อยู่ในตัวโมดูล tesaiot เอง

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

กับดักที่เจอบ่อย (ต่อ) — ขอบเขตของโมดูล tesaiot

อาการ สาเหตุที่แท้จริง วิธีแก้
เรียก device_id() health() random() แล้วได้ OSError "... failed" ตัวในกลุ่มสิบหกส่ง IPC ไปคอร์จอ ซึ่งทั้ง Eva และ Dev Kit ประกอบมาโดยไม่มี OPTIGA — คอร์จอตอบ "ไม่มีให้" กลับมา (เวลาที่เสียไปยังไม่ได้วัดจริง) อย่าเรียกกลุ่มนั้นบนบอร์ดไหนเลย · อยากคุยกับชิปจริงให้ใช้โมดูล optiga แทน
บน Dev Kit เรียก tesaiot.protected_update() แล้วคืน True ไม่มี error ฟังก์ชันนี้ทำงานจริงบน Dev Kit (ENABLE_OPTIGA_CLM=1) — มันขอชุด Protected Update จากแพลตฟอร์มแล้วเขียนใบรับรองลงชิป OPTIGA ห้ามเรียกในชุดบทเรียนนี้ ทั้งสองบอร์ด — บน Eva ได้ OSError แต่บน Dev Kit สิ่งที่เขียนลงชิปย้อนกลับเองไม่ได้
publish() คืน True แต่ข้อมูลไปโผล่ผิด topic สลับลำดับเป็น tesaiot.publish(topic, payload) ตามความเคยชินจากบทเรียน 4.4–4.6 ตัวนี้ payload มาก่อน เขียน tesaiot.publish(json.dumps(payload))
ตั้ง tls_mode เป็น "server_tls" แล้วอ่านกลับได้คนละคำ เฟิร์มแวร์แปลงชื่อโหมดให้เป็น "serverTLS" ปกติ ไม่ใช่บั๊ก เทียบค่าด้วยคำที่อ่านกลับมา อย่าเทียบกับคำที่เราส่งไป
if tesaiot.disconnect(): ทำงาน แต่ if mqtt.disconnect(): ไม่เคยจริง สองโมดูลคืนคนละชนิด ตัวแรกคืน bool ตัวหลังคืน None อย่าจำรวมกัน เปิดตารางดูทุกครั้งที่สลับโมดูล

สิบในสิบสามแถวของสองหน้านี้ ไม่ใช่บั๊กในโค้ด แต่เป็นความเข้าใจผิดเรื่องขอบเขตของ API — โค้ดถูกทุกตัวอักษร แต่สมมติฐานผิดหนึ่งข้อ

อ่านตารางนี้ ก่อน เจอปัญหา — แทบไม่มีอาการไหนมีข้อความ error ที่ชี้ไปหาสาเหตุ และแถว protected_update() คือแถวเดียวที่ "ไม่มี error" แปลว่าเสียหายไปแล้ว

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

ลงมือทำ — เติมช่องว่างในไฟล์ฝึก

s11_secure_telemetry.py pass ท่า 1 — config_set สามบรรทัด (ตัวตน) pass ท่า 1 — broker + sni_hostname pass ท่า 2 — ลูปรอ is_connected() + timeout pass ท่า 3 — payload ตัวเลขจริง pass ท่า 4 — lcd.print หลักฐานบนจอ

เปิด s11_secure_telemetry.py มีช่องว่างให้เติม 5 จุด

# เติม: tesaiot.config_set("device_id", DEVICE_ID) แล้วอีกสองบรรทัด api_key และ mqtt_pass
pass
# เติม: tesaiot.config_set("broker", BROKER) แล้วบรรทัดถัดไป sni_hostname เป็นชื่อเดียวกัน
pass
# เติม: while not tesaiot.is_connected(): เกิน 30000 ms ให้ break ไม่งั้น time.sleep_ms(500)
pass
    # เติม: payload = {"accel_x": round(m[0], 2), "heading": ..., "pot": ...}   # แบน ไม่ห่อ
    pass
    # เติม: lcd.print("ส่งครั้งที่", sent, "| โหมด", cfg["tls_mode"], "-> 8884")
    pass

หนึ่ง แก้ห้าบรรทัดบนหัวไฟล์ สอง เติมท่า 1 แล้วรันดู print(tesaiot.config()) สาม เติมท่า 2 แล้วจับเวลา สี่ เติมท่า 3-4 แล้วเปิด dashboard

tesaiot.connect() มีให้แล้วในไฟล์ ไม่ใช่ช่องว่าง — สิ่งที่เราต้องเขียนคือลูปที่รอมัน นั่นคือประเด็นของท่าที่ 2 ทั้งท่า

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

โบนัส · ตัวตนที่ฝังอยู่ในซิลิคอน (สาธิต ไม่บังคับ)

ชิปความปลอดภัยของ Infineon บนเมนบอร์ดโน้ตบุ๊ก

ภาพ: Raimond Spekking / Wikimedia Commons — CC BY-SA 4.0 — ชิปความปลอดภัยของ Infineon (SLB9655 บนเมนบอร์ดโน้ตบุ๊ก) เป็นคนละรุ่นกับ OPTIGA Trust M บนบอร์ดเรา แต่หน้าตาและหน้าที่เป็นตระกูลเดียวกัน

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

optiga.uid()          # เลขประจำตัวที่โรงงานเขียนไว้ อ่านได้ ลบไม่ได้
optiga.random(16)     # เลขสุ่มจากวงจรจริง ไม่ใช่จากสูตรในซอฟต์แวร์
optiga.sha256(b"...") # แฮชด้วยฮาร์ดแวร์
optiga.sign(...)      # เซ็นด้วยกุญแจที่ออกจากชิปไม่ได้

ความต่างที่สำคัญ: mqtt_pass ของวันนี้เป็นความลับที่ คัดลอกได้ ส่วนกุญแจส่วนตัวในชิปนี้ ออกจากชิปไม่ได้เลย ชิปยอมเซ็นให้เท่านั้น ใครขโมยบอร์ดไปได้ต้องขนบอร์ดไปทั้งตัว จะก๊อปตัวตนไปเฉย ๆ ไม่ได้

และในชิปยังมี ใบรับรองจากโรงงาน อ่านออกมาดูได้ — นั่นคือชิ้นส่วนที่ mTLS ต้องการ

นี่คือคำตอบของคำถามที่ค้างไว้เมื่อสองสไลด์ก่อน: ตัวตนของอุปกรณ์ที่ก๊อปไม่ได้ ต้องมาจากฮาร์ดแวร์ ไม่ใช่จากสตริงในไฟล์ Python

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

เชื่อมโยงรากฐาน + สรุปบทเรียน

ฝั่งสมองกลฝังตัว งบหน่วยความจำตอนจับมือ ค่าคงที่ที่คอมไพล์ติดมา ตัวตนที่มาจากซิลิคอน ฝั่งเครือข่ายและ Python กุญแจคู่ · ลายเซ็น · แฮช ใบรับรอง · ห่วงโซ่ · SNI API แบบ async ต้องรอเป็น ฝั่งออกแบบระบบ ขอบเขตของกลไกความปลอดภัย ตัวตนรายอุปกรณ์ ไม่ใช่รายรุ่น ค่าที่แสดง ≠ ค่าที่ใช้จริง

วันนี้เราได้: ส่ง telemetry ขึ้นแพลตฟอร์มผ่านช่องที่เข้ารหัส · อ่านการจับมือ TLS ได้ทีละขั้นและรู้ว่าใบรับรองพิสูจน์อะไร · รู้ว่าพอร์ตมาจาก tls_mode ไม่ใช่คีย์ port · และรอ API แบบ async เป็น แทนที่จะเชื่อค่าที่ฟังก์ชันคืนมา

สิ่งที่ติดตัวไปแม้เปลี่ยนภาษาและเปลี่ยนบอร์ด: นิสัยถามว่า "กลไกนี้พิสูจน์อะไร และไม่ได้พิสูจน์อะไร" ก่อนจะเชื่อมัน · การแยก "ฟังก์ชันคืนค่า" ออกจาก "งานเสร็จ" · และการไม่เชื่อค่าที่ตัวเองเพิ่งตั้งจนกว่าจะวัดที่ปลายทาง

งานทำเอง 30%: เลือกทำ 1 ข้อจากสี่ข้อในสไลด์ต่อยอด จดลงบันทึกการเรียน

ชุดบทเรียนถัดไป: capstone — ทีมเลือกโจทย์อุตสาหกรรมของตัวเอง แล้วประกอบ เซนเซอร์ → หน้าจอ → MQTT/MQTTs → แพลตฟอร์ม ให้ครบวงจรเป็นระบบเดียว

ชุดบทเรียนนี้เป็นบทเรียนแรกที่คำตอบที่ถูกที่สุดคือ "ได้ แต่แค่ระดับนี้" — และนั่นคือวิธีที่วิศวกรพูดถึงความปลอดภัยเสมอ

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

เฉลย s11_secure_telemetry.py — ส่วนที่หนึ่ง

TEAM_NAME = "BentoBuilders"
DEVICE_ID = "team03"              # ต้องตรงกับที่ขึ้นทะเบียน และสั้นกว่า 31 ตัวอักษร
API_KEY   = "<api key ของอุปกรณ์>"
MQTT_PASS = "<รหัสผ่าน MQTT 16 ตัว>"
BROKER    = "<โฮสต์แพลตฟอร์ม>"

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())
ค่าที่ต้องแก้อยู่ห้าบรรทัด รวมไว้บนหัวไฟล์ที่เดียว ข้างล่างไม่ต้องแตะเลย คนมาแก้ทีหลังหาเจอทันที BROKER โผล่สองที่ broker และ sni_hostname จึงใช้ตัวแปรตัวเดียว ทำให้ไม่มีทางไม่ตรงกัน print(config()) ก่อนต่อ เห็นค่าที่เฟิร์มแวร์เก็บจริง จับคีย์ที่สะกดผิดได้ตรงนี้ ถูกกว่าการไปเดาตอนต่อไม่ติด

เขียนค่าที่ต้องตรงกันไว้ที่เดียว แล้วบั๊กตระกูล "ลืมแก้ที่หนึ่ง" จะหายไปทั้งตระกูล — ชุดบทเรียนนี้ค่านั้นคือ BROKER

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

เฉลย — ตัวตนที่ตั้งไว้ ต้องมองเห็นได้ ไม่ใช่เชื่อเอา

cfg0 = tesaiot.config()
tbl_id = ui.Table(x=30, y=62, w=412, h=286, cols=2)
tbl_id.col_width(0, 140)
tbl_id.col_width(1, 260)
tbl_id.add_row("คีย์", "ค่าที่ตั้งไว้")
tbl_id.add_row("device_id", DEVICE_ID)
tbl_id.add_row("broker", BROKER)
tbl_id.add_row("tls_mode", str(cfg0["tls_mode"]))
ui.Label("mqtt_pass ตั้งแล้ว แต่อ่านกลับไม่ได้", x=30, y=356, color=COL_DIM, value=14)

led_wait = ui.Led(x=488, y=64, w=28, h=28, color=COL_RUN, value=1)
led_ok = ui.Led(x=588, y=64, w=28, h=28, color=COL_OK, value=0)
led_fail = ui.Led(x=688, y=64, w=28, h=28, color=COL_BAD, value=0)

bar_hs = ui.Bar(x=484, y=236, w=200, h=12, color=COL_RUN,
                min=0, max=WAIT_CEILING_S, value=0)
sc_hs = ui.Scale(x=484, y=250, w=200, h=44, color=COL_TEXT,
                 min=0, max=WAIT_CEILING_S)

config_set() เงียบสนิท ค่าที่ตั้งไปจึงไม่มีใครเห็น จนกว่าจะเอาขึ้นจอ print() ตอบได้เฉพาะคนที่นั่งอยู่หน้าคอม ส่วนตารางนี้ตอบคนที่เดินมาดูบอร์ด

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

ไฟสามดวงติดทีละดวง ไล่ตามจังหวะจริงของการจับมือ และ bar_hs เดินขึ้นเทียบกับ WAIT_CEILING_S ซึ่งเป็น ตัวเลขเดียวกับที่ลูป while ใช้ตัดสิน ไม่ใช่เลขที่วาดไว้ให้ดูสวย

คนที่ยืนรออยู่หน้าจอ 30 วินาที ต้องเห็นว่าโปรแกรมยังทำงาน จอที่นิ่งสนิทกับบอร์ดที่แฮงก์ หน้าตาเหมือนกันทุกประการ

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

เฉลย — ส่วนที่สอง: ลูปหลัก

tesaiot.connect()
t0 = time.ticks_ms()
last_s = -1
while not tesaiot.is_connected():
    waited = time.ticks_diff(time.ticks_ms(), t0)
    if waited > WAIT_CEILING_S * 1000:
        print("ต่อแพลตฟอร์มไม่สำเร็จใน 30 วินาที")
        break
    if waited // 1000 != last_s:          # ตัวเลขเปลี่ยนวินาทีละครั้ง ไม่ถี่กว่านั้น
        last_s = waited // 1000
        bar_hs.value(last_s)
        lbl_hs.text(str(last_s) + " วิ")
    ui.poll()
    time.sleep_ms(500)
...
while tesaiot.is_connected():
    try:
        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()}
    except OSError:
        time.sleep_ms(1000)
        continue
    tesaiot.publish(json.dumps(payload))
    sent += 1
    cfg = tesaiot.config()
    lcd.print("ส่งครั้งที่", sent, "| โหมด", cfg["tls_mode"], "-> 8884")
    lbl_sent.text("ส่งแล้ว " + str(sent) + " ใบ")
    for ev in ui.poll():
        ...        # ปุ่มต่อใหม่ / ตัดสาย พร้อมกล่องยืนยัน - อยู่ในไฟล์เฉลยเต็ม
    time.sleep_ms(5000)

เงื่อนไขของ while คือการเช็กสายก่อนทุกรอบส่ง ไม่ใช่เช็กครั้งเดียวตอนเริ่ม · สามค่าในลูปส่งอยู่ใน try เดียวกัน — อ่านพลาดหนึ่งรอบต้องไม่ทำให้หลุดการเชื่อมต่อ

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

เฉลย — ทำไมเรียงสี่ท่าแบบนี้

ท่า 1 — ตัวตนถูกต้อง ท่า 2 — ต่อเสร็จจริง ท่า 3 — ข้อมูลขึ้นกราฟ ท่า 4 — พิสูจน์ได้บนจอ

ท่า 1 มาก่อนเพราะตรวจได้โดยไม่ต้องใช้เน็ต — print(config()) บอกทันทีว่าค่าเข้าครบไหม · ท่า 2 แยกเป็นท่าของตัวเอง เพราะ "ต่อเสร็จ" เกิดทีหลังคำสั่ง ไม่ใช่ผลของคำสั่ง · ท่า 3 ส่งข้อมูลจริง เมื่อสองท่าแรกยืนยันแล้ว ถ้ากราฟยังว่าง รู้แน่ว่าปัญหาอยู่ที่รูปร่าง JSON · ท่า 4 คือหลักฐาน ที่ให้คนอื่นตรวจงานได้โดยไม่ต้องเปิดโค้ด

ท่า 5 คือปุ่มบนจอ — ตัดสาย เป็นคำสั่งที่ถอยกลับไม่ได้ทันที เพราะต้องจับมือ TLS ใหม่ทั้งชุด มันจึงมีกล่องยืนยันที่บอก สิ่งที่จะเกิด ไม่ใช่ถามลอย ๆ ว่า "แน่ใจไหม" ส่วน ต่อใหม่ ไม่ต้องยืนยัน เพราะถ้ากดพลาดก็แค่ต่อซ้ำ — ระดับของการยืนยัน มาจากราคาของความผิดพลาด ไม่ได้มาจากความสำคัญของปุ่ม

เรียงจาก "ตรวจง่ายที่สุด" ไป "ตรวจยากที่สุด" แล้วความล้มเหลวจะบอกที่อยู่ของตัวเองเสมอ

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

เชื่อมจุดให้เห็นภาพ — วันนี้อยู่ตรงไหนของเส้นทาง

บทเรียน 1.1–3.9 อ่านเซนเซอร์ วาดจอ ในบอร์ดของเราเอง บทเรียน 4.1–4.6 ต่อเน็ต แล้วส่งข้อมูล ออกไปให้เครื่องอื่นใช้ วันนี้ · บทเรียน 4.7–4.9 ช่องทางเข้ารหัส และรู้ว่าปลายทางเป็นตัวจริง บทเรียน 5.1–5.3 ประกอบทั้งหมด เป็นสินค้าหนึ่งชิ้น วันนี้คือจุดที่ "ระบบที่ใช้งานได้" กลายเป็น "ระบบที่ปล่อยออกนอกห้องได้"

คำถามคิดต่อ: ถ้ารหัส mqtt_pass ของอุปกรณ์เราหลุดออกไป จะรู้ตัวได้อย่างไร และควรทำอะไรเป็นอย่างแรก · ใครควรเป็นคนตัดสินใจว่าอุปกรณ์ตัวไหนถูกถอนสิทธิ์ ระหว่างคนดูแลแพลตฟอร์มกับคนเขียนเฟิร์มแวร์ · ถ้ามีอุปกรณ์ 10,000 ตัวและแต่ละตัวต้องมีตัวตนไม่ซ้ำกัน ขั้นตอนการ provision ที่โรงงานควรหน้าตาเป็นอย่างไร

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

ใช้จริงที่ไหน — สี่มุมที่ TLS ทำงานอยู่ตอนนี้

โรงพยาบาล · ข้อมูลที่กฎหมายคุ้มครอง เครื่องวัดสัญญาณชีพส่งค่าผ่านช่องที่เข้ารหัสเท่านั้น และต้องพิสูจน์ได้ว่าค่ามาจากเครื่องไหน เตียงไหน ระดับนี้ใช้ mTLS ไม่ใช่ serverTLS อย่างที่เราทำวันนี้ โรงงาน · เครื่องจักรที่สั่งงานได้จากไกล คำสั่งหยุดเครื่องต้องมาจากคนที่มีสิทธิ์เท่านั้น ปลอมคำสั่งได้ = อันตรายต่อความปลอดภัยของคน การเข้ารหัสอย่างเดียวไม่พอ ต้องพิสูจน์ตัวตนด้วย พลังงาน · มิเตอร์ที่ติดตั้งนอกบ้านคน อุปกรณ์อยู่ในมือคนอื่น จับต้องได้ แกะได้ รหัสในไฟล์จึงไม่พอ ต้องเป็นกุญแจในชิปที่ดึงออกไม่ได้ นี่คือเหตุผลที่ secure element มีอยู่บนโลก สินค้าผู้บริโภค · อัปเดตเฟิร์มแวร์ผ่านเน็ต ไฟล์อัปเดตต้องมีลายเซ็นที่อุปกรณ์ตรวจเองได้ ใช้กลไกเดียวกับใบรับรองที่เราเพิ่งเรียนวันนี้ TLS ปกป้องระหว่างทาง ลายเซ็นปกป้องตัวไฟล์เอง

สี่มุมนี้ใช้ชิ้นส่วนชุดเดียวกับที่เราเพิ่งเรียน ต่างกันที่ ตัวตนของอุปกรณ์มาจากไหน — ไฟล์ข้อความ หรือชิปที่ก๊อปไม่ได้

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

ดูเพิ่มเติมนอกเวลา — วิดีโอที่ตรวจแล้วว่าเปิดได้

Transport Layer Security (TLS) — Computerphile (Dr Mike Pound) · 15 นาที 33 วินาที · อังกฤษ — กรอบความคิดว่า TLS มีไว้ทำไม อธิบายสิ่งที่ต้องการปกป้องก่อนจะพูดถึงกลไก เหมาะดูก่อนคลิปถัดไป

TLS Handshake — EVERYTHING that happens when you visit an HTTPS website — Practical Networking · 27 นาที 58 วินาที · อังกฤษ — ทุกข้อความในการจับมือ พร้อม packet capture จริง ยาว ดูเป็นการบ้านหรือเลือกดูเฉพาะช่วง

อ่านและเล่นต่อสำหรับคนอยากรู้ลึก

คลิปเหล่านี้ไม่อยู่ในเกณฑ์ผ่าน แต่คนที่ดูจะออกแบบส่วนความปลอดภัยของ capstone ชุดบทเรียนถัดไปได้ลึกกว่าเพื่อน

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

ต่อยอด — คิดต่อเอง (เลือกทำ 1 ข้อ)

1 · จับเวลาสองท่อ 1883 เทียบ 8884 ส่วนต่างหายไปกับอะไร 2 · ทำให้พังอย่างมีระบบ แก้ผิดทีละหนึ่งค่า ทำตารางอาการของทีมเอง 3 · ตัวตนที่มาจากชิป optiga.uid() · random() ทำไม uid ยังไม่พอ 4 · schema ของงานจริง ส่งอะไร ถี่แค่ไหน คำนวณข้อมูลต่อวัน

ข้อ 1 · จับเวลาสองท่อ — วัดด้วย time.ticks_ms() ว่าจากสั่ง connect() จนถึง is_connected() เป็น True ใช้เวลากี่มิลลิวินาที ทำสามรอบแล้วหาค่ากลาง เทียบกับเวลาที่ mqtt.connect() ของบทเรียน 4.4–4.6 ใช้ แล้วอธิบายว่าส่วนต่างนั้นหายไปกับอะไรบ้าง

ข้อ 2 · ทดลองทำให้พังอย่างมีระบบ — แก้ค่าให้ผิด ทีละหนึ่งตัว: device_id ผิดหนึ่งตัวอักษร / mqtt_pass ผิด / sni_hostname ผิด — จดว่าแต่ละแบบให้อาการต่างกันอย่างไร แล้วทำตารางอาการ → สาเหตุของทีมเอง

ข้อ 3 · ตัวตนที่มาจากชิป — เปิด REPL เรียก optiga.uid(), optiga.random(16) สามครั้ง และ optiga.sha256(b"...") จดผลไว้ แล้วตอบว่าทำไมเลขสุ่มจากชิปถึงเชื่อถือได้มากกว่าเลขสุ่มจากสูตรในซอฟต์แวร์ และทำไม uid() ถึงยังไม่พอที่จะเป็นตัวตนสำหรับ mTLS (บน Dev Kit ให้เรียก uid() ก่อนเป็นข้อแรก — ถ้าบอร์ดนั้นไม่มีชิป ข้อนี้ทำไม่ได้ และห้ามแตะ tesaiot.protected_update() ในข้อนี้เด็ดขาด)

ข้อ 4 · ออกแบบ schema ของงานจริง — สมมติทีมต้องส่ง telemetry ของเครื่องจักรจริงหนึ่งเครื่อง ออกแบบว่าจะส่งฟิลด์อะไรบ้าง ถี่แค่ไหน ฟิลด์ไหนควรส่งเฉพาะตอนผิดปกติ พร้อมคำนวณปริมาณข้อมูลต่อวัน

เขียนคำตอบลงบันทึกการเรียน แล้วเอามาเล่าให้เพื่อนฟังต้นชุดบทเรียนถัดไป

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

ปิดวงจร — ค่าที่วัดได้จริง ออกไปแบบที่คนกลางอ่านไม่ได้

หกไฟล์ที่ผ่านมาสอนกลไก TLS ด้วยค่าที่แต่งขึ้น เพื่อให้เห็นการจับมือชัด ๆ 07_real_reading_over_tls.py ปิดวง — ค่าที่ออกไปคือค่าที่ชิปบนบอร์ดวัดได้จริง · ค่านั้นคืออุณหภูมิ อ่านผ่าน read_temp() ในไฟล์: บน Dev Kit มาจาก SHT40 จริง ส่วน บน Eva ไม่มีเซนเซอร์อุณหภูมิ ไฟล์จึงให้ลูกบิดเล่นบทแทน (0–100 % = 15–45 °C) และบอกไว้บน console ทั้งสองกรณีว่าค่ามาจากไหน — sensors.snapshot() ไม่มีช่องอุณหภูมิบนบอร์ดไหนเลย

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

นี่คือเหตุผลที่บทเรียน 2.7–2.9 กับ 3.1–3.3 ต้องมาก่อนชุดบทเรียนนี้ ไม่ใช่มาทีหลัง — ความน่าเชื่อถือของระบบเริ่มที่เซนเซอร์ ไม่ได้เริ่มที่ใบรับรอง

หน้าจอของ 07_real_reading_over_tls.py: ค่าที่วัดได้จริง ออกไปแบบที่คนกลางอ่านไม่ได้

ภาพจากตัวจำลอง bento_sim — โค้ด CM55 ชุดเดียวกับที่รันบนบอร์ด ที่ 800x480 เท่าจอของทั้งสองบอร์ด · ค่าอุณหภูมิและสถานะลิงก์เป็นค่าแทนบนเครื่องโฮสต์ ไม่ใช่ผลการวัดของบอร์ด

ลองบนโต๊ะ: เปิด MQTT Explorer ที่ปลายทาง เทียบว่าเลขที่เห็นตรงกับเลขบนจอบอร์ดไหม

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

หน้าจอของทุกไฟล์ในชุดบทเรียนนี้ (1/2)

หน้าจอของ 01_config_store.py: คลังค่าตั้งของแพลตฟอร์ม อ่านให้ครบก่อนจะต่ออะไร หน้าจอของ 02_config_reset_reload.py: ล้างค่าตั้ง กับ ย้อนค่าตั้ง เป็นคนละเรื่องกัน หน้าจอของ 03_slots_and_the_dead_half.py: ครึ่งที่ตอบทันที กับ ครึ่งที่ต้องมีชิป OPTIGA

01 คลังค่าตั้งของแพลตฟอร์ม อ่านให้ครบก่อนจะต่ออะไร · 02 ล้างค่าตั้ง กับ ย้อนค่าตั้ง เป็นคนละเรื่องกัน · 03 ครึ่งที่ตอบได้ กับ ครึ่งที่ข้ามคอร์ไปหาชิปที่ไม่ได้เปิด — จับเวลาให้ดู
ภาพจากตัวจำลอง bento_sim ซึ่งเรนเดอร์ด้วยโค้ด CM55 ชุดเดียวกับที่รันบนบอร์ด ที่ 800x480 เท่าจอของทั้งสองบอร์ด — แสดงหน้าจอที่ตัวอย่างสร้าง ค่าจากเซนเซอร์ WiFi และไมค์เป็นค่าแทนบนเครื่องโฮสต์ ไม่ใช่ผลการวัดของบอร์ด
TESA Open Knowledge · © 2026 สมาคมสมองกลฝังตัวไทย (TESA) · ดัดแปลงจาก AIoT in Action (AIC มหาวิทยาลัยบูรพา) · CC BY-NC 4.0

หน้าจอของทุกไฟล์ในชุดบทเรียนนี้ (2/2)

หน้าจอของ 04_disconnect_and_republish.py: ปิดงานให้เรียบร้อย แล้วเปิดใหม่ หน้าจอของ 05_wait_for_connected.py: connect() คืนค่าก่อนต่อเสร็จ ต้องรอด้วย is_connected() หน้าจอของ 06_secure_publish_loop.py: ส่งขึ้นแพลตฟอร์มผ่าน TLS แล้วโชว์หลักฐานบนจอ

04 ปิดงานให้เรียบร้อย แล้วเปิดใหม่ · 05 connect() คืนค่าก่อนต่อเสร็จ ต้องรอด้วย is_connected() · 06 ส่งขึ้นแพลตฟอร์มผ่าน TLS แล้วโชว์หลักฐานบนจอ
ภาพจากตัวจำลอง bento_sim ซึ่งเรนเดอร์ด้วยโค้ด CM55 ชุดเดียวกับที่รันบนบอร์ด ที่ 800x480 เท่าจอของทั้งสองบอร์ด — แสดงหน้าจอที่ตัวอย่างสร้าง ค่าจากเซนเซอร์ WiFi และไมค์เป็นค่าแทนบนเครื่องโฮสต์ ไม่ใช่ผลการวัดของบอร์ด
TESA Open Knowledge · © 2026 สมาคมสมองกลฝังตัวไทย (TESA) · ดัดแปลงจาก AIoT in Action (AIC มหาวิทยาลัยบูรพา) · CC BY-NC 4.0

อ้างอิงและเครดิต

มาตรฐานและเอกสารโพรโทคอล

วิดีโอ

ภาพ (ทุกไฟล์เก็บไว้ในโฟลเดอร์ img/ ของบทเรียน 4.7–4.9 ไม่ได้ลิงก์ข้ามเว็บ)

  • Wikimedia Commons — สาธารณสมบัติ: s11_keypair_encrypt.svg, s11_signature_verify.svg (Davidgothberg) · s11_hash_function.svg (Jorge Stolfi ต่อยอดจาก Helix84) · s11_tls12_handshake.svg, s11_tls13_handshake.svg (Fleshgrinder และ The Tango! Desktop Project)
  • Wikimedia Commons — CC BY-SA 4.0: s11_chain_of_trust.svg (Yuhkih) · s11_mitm_tls_inspection.svg (Rudolf.Achter) · s11_mqtt_broker_tls.svg (Ademant) · s11_secure_element.jpg (Raimond Spekking) — CC BY 3.0: s11_mutual_auth.svg (Essich)
  • s11_tls12_handshake_th.svg — วาดเองสำหรับหลักสูตรนี้ ดัดแปลงลำดับเวลาจาก Full TLS 1.2 Handshake (สาธารณสมบัติ) แปลป้ายเป็นไทยและเปลี่ยนปลายทางเป็น EMQX
  • s11_ce_dashboard.png, s11_ce_devices.png — ภาพหน้าจอ TESAIoT Community Edition v1.1.8, docs/images/screenshots/ (Apache-2.0)

ข้อเท็จจริงของเฟิร์มแวร์และแพลตฟอร์ม

พอร์ต 8884 ถูกเลือกจาก tls_mode ไม่ใช่จากคีย์ port · root CA ถูกคอมไพล์เข้าเฟิร์มแวร์และเปลี่ยนตอนรันไม่ได้ · tesaiot.connect() เป็น API แบบ asynchronous ต้องรอด้วย is_connected() · device_id ยาวได้ 31 ตัวอักษรฝั่งบอร์ด — ตรวจจาก modtesaiot.c ใน BENTO-TESAIoT-libraries ซึ่งเป็นโค้ดร่วมของทั้ง Eva Kit และ TESAIoT Dev Kit (นับชื่อได้ 28 จากตาราง globals) · protected_update() ทำงานจริงเฉพาะบิลด์ที่ ENABLE_OPTIGA_CLM=1 — Dev Kit ตั้งค่านี้ไว้ ส่วน Eva ตั้ง 0 (proj_cm33_ns/Makefile ของแต่ละโปรเจกต์) · สิบหกชื่อที่ข้ามคอร์ตายทั้งสองบอร์ดเพราะ proj_cm55/Makefile ของทั้งคู่ตั้ง ENABLE_OPTIGA ?= 0 · ข้อเท็จจริงที่ว่า TESAIoT CE สร้าง root CA ใหม่แบบสุ่มทุกการติดตั้ง (scripts/init-vault-pki.sh) จึงใช้ MQTTs กับ CE ที่ self-host ไม่ได้จนกว่าจะ build เฟิร์มแวร์ใหม่ — ตรวจจาก repo tesaiot/tesaiot-community-edition เมื่อ 2026-08-12

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

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

ระหว่างรอ แถบเดินขึ้นทุกครึ่งวินาที ตัวเลขเขียนใหม่แค่ตอนวินาทีเปลี่ยน — คนที่ยืนรอต้องเห็นว่าโปรแกรมยังทำงาน