import tesaiot ได้มา 28 ชื่อ — ใช้ได้จริงเก้าตัว ทั้ง Eva Kit และ Dev Kitพูดให้ตรง: ทั้งสองบอร์ดประกอบเฟิร์มแวร์ของคอร์จอด้วย 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()ก่อน
| เรียกอย่างไร | คืนอะไร | สิ่งที่ต้องรู้ |
|---|---|---|
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())หนึ่งครั้งหลังตั้งค่าเสมอ
# --- ท่าที่ 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 ตัวอักษร เกินแล้วถูกตัดเงียบ แล้วแพลตฟอร์มจะหาอุปกรณ์ไม่เจอ
sni_hostnameไม่ใช่ค่าเสริม — มันคือชื่อที่ทำให้เซิร์ฟเวอร์ หยิบใบรับรองใบที่ถูกมายื่นให้เรา ตั้งไม่ตรงเมื่อไร การจับมือล้มโดยไม่มีข้อความบอก
# --- 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)
tesaiot.connect() เป็น API แบบ asynchronous คืนค่าทันทีเพื่อไม่บล็อกโปรแกรม — ค่าที่คืนมา ไม่ใช่สถานะสุดท้าย สิ่งเดียวที่ตอบได้ว่าต่อเสร็จหรือยังคือ tesaiot.is_connected() · โจทย์ต่อ: ให้ลบลูปนี้ออกแล้วรันหนึ่งรอบ — จะเห็นด้วยตาว่า publish ที่ยิงเร็วเกินไป ไม่ error และไม่ถึงแพลตฟอร์ม
"ฟังก์ชันคืนค่าแล้ว" กับ "งานเสร็จแล้ว" เป็นคนละเรื่องเสมอสำหรับ API แบบ async — ความต่างนั้นวัดได้เป็นวินาที
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")
tesaiot.publish(payload) รับ ตัวข้อมูลอย่างเดียว — ต่างจาก mqtt.publish(topic, payload) ของชุดบทเรียนที่แล้ว เพราะเฟิร์มแวร์ประกอบ topic ให้เองจาก device_id ที่เราตั้งไว้ในท่าที่ 1 (จะระบุ topic เองก็ได้ แต่ชุดบทเรียนนี้ไม่ต้อง) · ส่ง ตัวเลขจริง ไม่ใช่สตริง ไม่งั้น dashboard จะขึ้นค่าแต่วาดกราฟไม่ได้ — กับดักเดียวกับบทเรียน 4.4–4.6
จอบอร์ดต้องตอบคำถาม MVP ได้เอง: ทีมไหน · โหมดอะไร · ส่งไปกี่ครั้งแล้ว สามอย่างนี้ทำให้คนอื่นตรวจงานเราได้โดยไม่ต้องถาม
ระบบยาวหกกล่อง แต่การดีบักไม่เคยยาวกว่า "หาให้เจอว่ากล่องสุดท้ายที่ยังเห็นข้อมูลคือกล่องไหน" — ชุดบทเรียนนี้กล่องแรกที่ต้องตรวจคือ
is_connected()
# --- ล้างแล้วตั้งใหม่: ทางออกเมื่อตั้งค่ามั่วจนไม่รู้ว่าเหลืออะไรอยู่ ---
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() ล้างของจริง ส่วน 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ปิดแล้วเปิดใหม่
wifi.connect() ของบทเรียน 4.1–4.3 บล็อกได้นานถึงราว 85 วินาทีถ้ารหัสผิด)s11_secure_telemetry.py แก้ห้าบรรทัดบนหัวไฟล์ให้เป็นค่าของอุปกรณ์คุณprint(tesaiot.config()) ขึ้นค่าครบและสะกดถูก ก่อน ไปท่าที่ 2is_connected() จึงเป็น True แล้วจดลงบันทึกการเรียนต้องทำในบทเรียน · เปิดตามลำดับนี้ ทั้งชุดราว 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 อยู่ตรงกลาง จุดที่พังได้มีมากกว่าชุดบทเรียนก่อนหน้า และไม่มีจุดไหนส่งเสียงเลย
ที่ต้องบอกกันไว้ก่อน เพราะถ้าไม่บอก ผู้เรียนจะไปเจอเองตอนนั่งดีบักแล้วเข้าใจว่าตัวเองเขียนผิด ทั้งที่ไม่ได้ผิดเลย · แหล่งที่ดู: 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")