
บน 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
วันนี้เราได้: ส่ง telemetry ขึ้นแพลตฟอร์มผ่านช่องที่เข้ารหัส · อ่านการจับมือ TLS ได้ทีละขั้นและรู้ว่าใบรับรองพิสูจน์อะไร · รู้ว่าพอร์ตมาจาก tls_mode ไม่ใช่คีย์ port · และรอ API แบบ async เป็น แทนที่จะเชื่อค่าที่ฟังก์ชันคืนมา
สิ่งที่ติดตัวไปแม้เปลี่ยนภาษาและเปลี่ยนบอร์ด: นิสัยถามว่า "กลไกนี้พิสูจน์อะไร และไม่ได้พิสูจน์อะไร" ก่อนจะเชื่อมัน · การแยก "ฟังก์ชันคืนค่า" ออกจาก "งานเสร็จ" · และการไม่เชื่อค่าที่ตัวเองเพิ่งตั้งจนกว่าจะวัดที่ปลายทาง
งานทำเอง 30%: เลือกทำ 1 ข้อจากสี่ข้อในสไลด์ต่อยอด จดลงบันทึกการเรียน
ชุดบทเรียนถัดไป: capstone — ทีมเลือกโจทย์อุตสาหกรรมของตัวเอง แล้วประกอบ เซนเซอร์ → หน้าจอ → MQTT/MQTTs → แพลตฟอร์ม ให้ครบวงจรเป็นระบบเดียว
ชุดบทเรียนนี้เป็นบทเรียนแรกที่คำตอบที่ถูกที่สุดคือ "ได้ แต่แค่ระดับนี้" — และนั่นคือวิธีที่วิศวกรพูดถึงความปลอดภัยเสมอ
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
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 วินาที ต้องเห็นว่าโปรแกรมยังทำงาน จอที่นิ่งสนิทกับบอร์ดที่แฮงก์ หน้าตาเหมือนกันทุกประการ
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เดียวกัน — อ่านพลาดหนึ่งรอบต้องไม่ทำให้หลุดการเชื่อมต่อ
ท่า 1 มาก่อนเพราะตรวจได้โดยไม่ต้องใช้เน็ต — print(config()) บอกทันทีว่าค่าเข้าครบไหม · ท่า 2 แยกเป็นท่าของตัวเอง เพราะ "ต่อเสร็จ" เกิดทีหลังคำสั่ง ไม่ใช่ผลของคำสั่ง · ท่า 3 ส่งข้อมูลจริง เมื่อสองท่าแรกยืนยันแล้ว ถ้ากราฟยังว่าง รู้แน่ว่าปัญหาอยู่ที่รูปร่าง JSON · ท่า 4 คือหลักฐาน ที่ให้คนอื่นตรวจงานได้โดยไม่ต้องเปิดโค้ด
ท่า 5 คือปุ่มบนจอ — ตัดสาย เป็นคำสั่งที่ถอยกลับไม่ได้ทันที เพราะต้องจับมือ TLS ใหม่ทั้งชุด มันจึงมีกล่องยืนยันที่บอก สิ่งที่จะเกิด ไม่ใช่ถามลอย ๆ ว่า "แน่ใจไหม" ส่วน ต่อใหม่ ไม่ต้องยืนยัน เพราะถ้ากดพลาดก็แค่ต่อซ้ำ — ระดับของการยืนยัน มาจากราคาของความผิดพลาด ไม่ได้มาจากความสำคัญของปุ่ม
เรียงจาก "ตรวจง่ายที่สุด" ไป "ตรวจยากที่สุด" แล้วความล้มเหลวจะบอกที่อยู่ของตัวเองเสมอ
คำถามคิดต่อ: ถ้ารหัส mqtt_pass ของอุปกรณ์เราหลุดออกไป จะรู้ตัวได้อย่างไร และควรทำอะไรเป็นอย่างแรก · ใครควรเป็นคนตัดสินใจว่าอุปกรณ์ตัวไหนถูกถอนสิทธิ์ ระหว่างคนดูแลแพลตฟอร์มกับคนเขียนเฟิร์มแวร์ · ถ้ามีอุปกรณ์ 10,000 ตัวและแต่ละตัวต้องมีตัวตนไม่ซ้ำกัน ขั้นตอนการ provision ที่โรงงานควรหน้าตาเป็นอย่างไร
สี่มุมนี้ใช้ชิ้นส่วนชุดเดียวกับที่เราเพิ่งเรียน ต่างกันที่ ตัวตนของอุปกรณ์มาจากไหน — ไฟล์ข้อความ หรือชิปที่ก๊อปไม่ได้
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 ชุดบทเรียนถัดไปได้ลึกกว่าเพื่อน
ข้อ 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 ของเครื่องจักรจริงหนึ่งเครื่อง ออกแบบว่าจะส่งฟิลด์อะไรบ้าง ถี่แค่ไหน ฟิลด์ไหนควรส่งเฉพาะตอนผิดปกติ พร้อมคำนวณปริมาณข้อมูลต่อวัน
เขียนคำตอบลงบันทึกการเรียน แล้วเอามาเล่าให้เพื่อนฟังต้นชุดบทเรียนถัดไป
หกไฟล์ที่ผ่านมาสอนกลไก 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 ต้องมาก่อนชุดบทเรียนนี้ ไม่ใช่มาทีหลัง — ความน่าเชื่อถือของระบบเริ่มที่เซนเซอร์ ไม่ได้เริ่มที่ใบรับรอง

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


มาตรฐานและเอกสารโพรโทคอล
วิดีโอ
ภาพ (ทุกไฟล์เก็บไว้ในโฟลเดอร์ img/ ของบทเรียน 4.7–4.9 ไม่ได้ลิงก์ข้ามเว็บ)
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)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 (สาธารณสมบัติ) แปลป้ายเป็นไทยและเปลี่ยนปลายทางเป็น EMQXs11_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 ได้เลย
ระหว่างรอ แถบเดินขึ้นทุกครึ่งวินาที ตัวเลขเขียนใหม่แค่ตอนวินาทีเปลี่ยน — คนที่ยืนรอต้องเห็นว่าโปรแกรมยังทำงาน