
examples/s02/05_value_leaves_the_board.py↗ บน BENTO Emulator ที่ 800x480 เท่าจอของทั้งสองบอร์ด — ภาพนี้ถ่ายตอนที่ Emulator ยังใช้ broker จำลอง ค่าในภาพจึงไม่ได้ออกไปไหนจริง ภาพนี้แสดงหน้าตาจอ ไม่ใช่หลักฐานว่าส่งถึง · ภาพนี้เก่า ในภาพ broker ยังเป็นเลขแลน 192.168.1.50 และหัวข้อเป็น bento/eva-team03/telemetry ส่วนไฟล์ปัจจุบันใช้ broker.hivemq.com กับหัวข้อ bento-aiot/team03/telemetry ส่ง 60 ใบ และส่งค่าเอียง az ด้วย รอถ่ายใหม่broker.hivemq.com)broker.hivemq.com — ถ้าถูกกัน ขั้นที่ 2 จะเป็นแดงพร้อมบอกเหตุผล แล้วโปรแกรมจบตรงนั้นอย่างสุภาพ ส่วนฝั่งผู้รับ วันนี้คือหน้าเว็บ examples/web/my_first_reader.html ไม่ต้องลงโปรแกรมใดในเครื่องยังไม่มีใครรันไฟล์ 05 กับ 06 บนบอร์ดกับ broker.hivemq.com สิ่งที่วัดแล้วคือเวลาไปกลับของ broker จาก Mac บนโต๊ะผู้สอน ไม่ใช่จากเน็ตมหาวิทยาลัย ถ้าไปไม่ถึง ไฟล์จะขึ้นบนจอตรง ๆ ว่าหยุดที่ขั้นไหนและเพราะอะไร — ผลแบบนั้นก็ยังเป็นผลที่ใช้ได้
สี่โมดูลใหม่วันนี้ทำหน้าที่คนละอย่างกันชัดเจน wifi พาบอร์ดขึ้นวง · mqtt ส่งข้อความออกและรับกลับ · json แปลงข้อมูลของเราให้เป็นข้อความที่ทุกภาษาอ่านได้ · tesaiot เก็บค่าตั้งไว้บนแฟลชของบอร์ดเอง
ที่เหลือเป็นของเดิม sensors อ่านลูกบิด gpio สั่งหลอดไฟ ส่วน lcd ui time คือชุดเดิมจากคาบที่แล้วทุกประการ
ไม่ต้องจำรายชื่อนี้ตอนนี้ แต่ละไฟล์จะแนะนำของของมันเองตอนที่ถึงคิว
wifi.connect() แล้วอ่านหมายเลข IP ของบอร์ดตัวเองได้wifi.scan() แล้ววินิจฉัยเองได้ว่าต่อไม่ติดเพราะอะไรปลายทางของวันนี้: จอบอร์ดขึ้นหมายเลข IP ของทีม เลขใบที่ส่งออกไปเดินขึ้น และหลอดไฟบนบอร์ดติดเพราะมีคนพิมพ์คำสั่งมาจากอีกเครื่อง
เปิดบอร์ดรอไว้ได้เลย เล่นเกมเปิดคาบเสร็จ เรารันไฟล์แรกทันที — คาบนี้ทฤษฎีมาทีหลังของจริงทุกเรื่อง
WiFi ของคณะต้อง login ผ่านหน้าเว็บก่อนใช้งาน บอร์ดไม่มีเบราว์เซอร์ให้กดยอมรับ บอร์ดจะได้เลข IP แต่ออกอินเทอร์เน็ตไม่ได้ ไปไม่ถึง broker · วันนี้บอร์ดของทุกทีมต่อ Hotspot จากมือถือของคนในทีมหนึ่งเครื่อง
| ขั้น | iPhone | Android |
|---|---|---|
| 1 · ตั้งชื่อ | ชื่อ Hotspot คือชื่อเครื่อง: การตั้งค่า > ทั่วไป > เกี่ยวกับ > ชื่อ ตั้งเป็น bento-team05 |
การตั้งค่า Hotspot > ชื่อเครือข่าย ตั้งเป็น bento-team05 |
| 2 · ตั้งรหัส | ฮอตสปอตส่วนบุคคล > รหัสผ่าน Wi-Fi (อย่างน้อย 8 ตัว) | รหัสผ่าน อย่างน้อย 8 ตัว ความปลอดภัย WPA2 |
| 3 · ย่านความถี่ | เปิด เพิ่มความเข้ากันได้สูงสุด (Maximize Compatibility) | เลือกย่าน 2.4 GHz ถ้ามีให้เลือก |
| 4 · เปิดค้าง | เปิดหน้าฮอตสปอตค้างไว้ตอนบอร์ดต่อครั้งแรก | เปิด Hotspot ค้างไว้ |
แล้วแก้สองบรรทัดบนหัวทุกไฟล์ที่ต่อเน็ตให้ตรงกับมือถือ:
WIFI_SSID = "bento-team05" # ชื่อ Hotspot ตัวอักษรอังกฤษ ไม่มีช่องว่าง
WIFI_PASS = "<รหัส Hotspot ของทีม>"
สมชาย’s iPhone มีอักขระที่จอวาดไม่ได้ และยาวจนข้อความบนจอถูกตัดยังไม่ได้ทดสอบกับ Hotspot ครบทุกรุ่น ถ้าบอร์ดหา Hotspot ไม่เจอ ให้เช็กข้อ 3 และข้อ 4 ก่อน แล้วลองรันไฟล์
04_scan_the_room.pyดูว่าบอร์ดได้ยินชื่อนั้นไหม
เปิดหน้ารวม https://advance-innovation-centre-aic.github.io/embedded-systems-for-aiot-developer/examples/web/mqtt_dashboard.html บนเครื่องตัวเอง ไม่ต้องติดตั้งอะไร หน้าจะต่อ broker เอง ช่อง ทีม ในหัวข้อ "ส่งคำสั่ง" พิมพ์ team00 แล้วกด บี๊บ หรือพิมพ์ข้อความสั้น ๆ แล้วกด ส่งข้อความ (say) บอร์ดบนโต๊ะผู้สอนรัน examples/s02/06_command_comes_back.py↗ เป็น team00 อยู่ มันจะร้อง หรือขึ้นข้อความของคุณ (ตัดที่ 24 ตัวอักษร)
ทายก่อน แล้วค่อยดู ผู้สอนนับหนึ่งถึงสาม ทุกคนกดส่งพร้อมกัน ทีมเขียนคำทายลงใบงานข้อ 4.5 ก่อน เขียนแล้วห้ามแก้
| ทีมเราทายว่าตัวเลข "ได้รับแล้ว" บนจอบอร์ดหน้าห้องจะขึ้น | ☐ น้อยกว่า 10 | ☐ 10 ถึง 30 | ☐ ครบทุกคน |
|---|
พิมพ์ภาษาไทยหรืออังกฤษเท่านั้น จอบอร์ดไม่มีตัวอักษรอื่น · ส่งจากหน้าเว็บ · ยังไม่มีใครนับว่าจริง ๆ จะขึ้นกี่ใบ ผลที่เห็นในห้องคือคำตอบ แล้วเราจะกลับมาดูว่าทำไมที่สไลด์ไฟล์ 06
เปิด examples/s02/01_wifi_first_connect.py↗ แก้สองบรรทัดบนสุดให้ตรงกับเครือข่ายที่ผู้สอนแจกหน้าห้อง แล้วส่งขึ้นบอร์ดเลย
# แก้สองบรรทัดนี้ให้ตรงกับเครือข่ายที่ผู้สอนแจกหน้าห้อง
WIFI_SSID = "bento-teamXX" # ชื่อ Hotspot มือถือของทีม (WiFi คณะต้อง login บอร์ดใช้ไม่ได้)
WIFI_PASS = "<รหัส Hotspot ของทีม>" # อย่างน้อย 8 ตัว
สิ่งที่ต้องจดระหว่างรอ: จอค้างนานแค่ไหน (นับในใจก็ได้) แล้วเทียบกับตัวเลข ms ที่บอร์ดรายงานตอนกลับมา ตรงกันไหม
ยังไม่เข้าใจว่าทำไมต้องรอ ไม่เป็นไร สองสไลด์ถัดไปเราจะแกะทีละบรรทัด แต่ต้องเห็นมันเกิดขึ้นกับตาก่อน
ทีมที่ต่อติดตั้งแต่ยี่สิบนาทีแรก จะมีเวลาเหลือไปถึงไฟล์ 05 กับ 06 อย่างสบาย ๆ ซึ่งเป็นสองไฟล์ที่สนุกที่สุดของคาบ
wifi มีแปดตัว — ห้าตัวนี้ใช้วันนี้สามตัวบนใช้ตอนต่อ สองตัวล่างใช้ตอนสำรวจและตอนหาสาเหตุว่าทำไมต่อไม่ติด
อีกสามตัวที่เหลือ — wifi.disconnect() ตัดการเชื่อมต่อเอง · wifi.ping(host) วัดว่าปลายทางตอบไหมและช้าแค่ไหน · wifi.softap() เปลี่ยนบอร์ดเป็นตัวปล่อยสัญญาณเองเมื่อไม่มีเราเตอร์ให้เกาะ ทั้งสามอยู่ในคาบ 9 (examples/s09/04, 07, 08)
ตรวจเองได้เสมอว่าโมดูลไหนมีอะไร — พิมพ์
print(dir(wifi))บนบอร์ด แล้วนับเอง อย่าเชื่อสไลด์รวมทั้งแผ่นนี้
lcd ui และ time ยังเป็นชุดเดิมจากคาบที่แล้วทุกประการ — ui.poll() หลังแก้ widget · ui.Seg7 รับข้อความ · ui.Chart เกิดมาพร้อมเส้นที่ 0 อยู่แล้ว · lcd.print() ส่งได้ 127 ไบต์ต่อครั้ง ส่วนป้าย ui พาได้ 126 ไบต์ทั้งตอนสร้างและตอนเรียก .text()
ความต่างของสามตัวบนอยู่ที่ เวลาของคำถาม ไม่ใช่ที่ข้อมูลที่มันคืนมา จำประโยคนี้ไว้แล้วทั้งคาบจะง่ายขึ้น
step_lbl = ui.Label("ป้ายนี้ขึ้นก่อน แล้วจอจะนิ่ง", x=24, y=208, color=COL_TEXT,
value=20)
...
ui.poll() # เคาะให้ป้ายขึ้นจอจริง ๆ ก่อนเข้าบรรทัดที่บล็อก
...
t0 = time.ticks_ms()
ok = wifi.connect(WIFI_SSID, WIFI_PASS)
elapsed = time.ticks_diff(time.ticks_ms(), t0)
...
seg.text(str(elapsed)) # Seg7 รับข้อความ จึงคุมรูปแบบได้ seg.value(4218) ได้แค่จำนวนเต็ม
ป้ายที่สร้างไว้แต่ยังไม่ได้เคาะ จะไปโผล่ หลัง connect() จบ ซึ่งคือตอนที่ไม่มีใครต้องการมันแล้ว กฎ ui.poll() จากคาบที่แล้วกลายเป็นเรื่องเป็นเรื่องตายขึ้นมาตรงนี้
time.ticks_ms() กับ ticks_diff() เป็นคู่เดิมจากไฟล์ 07 ของคาบที่แล้ว ที่ต่างคือคราวนี้เราเอามันมาคร่อมบรรทัดที่ควบคุมไม่ได้ เพราะ "นานจัง" ไม่ใช่ข้อมูล ส่วน "4218 ms" เอาไปเทียบกันได้ทั้งระหว่างทีมและระหว่างรหัสถูกกับรหัสผิด
ถ้า
okเป็นFalseไฟล์นี้รายงานแล้วraise SystemExitทันที โปรแกรมที่พูดเฉพาะตอนสำเร็จ จะเงียบในจังหวะที่คนอยากรู้ที่สุด
"0.0.0.0" — และทำไมรหัสผิดถึงช้ากว่ารหัสถูกip = wifi.ip()
# ip() คืนสตริงเสมอ ตอนยังไม่มีที่อยู่มันคืน "0.0.0.0"
# ซึ่งเป็นสตริงที่ if ถือว่าจริง — ต้องเทียบตรง ๆ เท่านั้น
if ip == "0.0.0.0":
note.text("ลิงก์ขึ้นแล้วแต่ยังไม่ได้เลข IP กำลังรอ DHCP")
การเข้าร่วมเครือข่ายกับการได้เลขที่อยู่เป็นคนละขั้น ลิงก์อาจขึ้นแล้วแต่ยังไม่มีใครแจกเลขให้ ช่วงนั้น wifi.ip() คืน "0.0.0.0" ซึ่ง Python ถือว่าจริง เพราะมันไม่ใช่สตริงว่าง เขียน if wifi.ip(): จึงผ่านทั้งที่ยังส่งอะไรออกไม่ได้
นี่คือความล้มเหลวแบบที่ ไม่มี error ให้จับ โปรแกรมเดินผ่านไปตามปกติ แล้วรายงานเลขที่อยู่ปลอมขึ้นจออย่างมั่นใจ
ตาคุณ อยู่ท้ายไฟล์ 01 พิมพ์รหัสผ่านผิดไปหนึ่งตัวแล้วรันใหม่ จับเวลาเทียบกับรอบที่ถูก แล้วตอบว่าทำไมกรณีผิดถึงใช้เวลา มากกว่า ไม่ใช่น้อยกว่า
ความล้มเหลวแพงกว่าความสำเร็จเสมอในงานเครือข่าย เพราะมีการลองใหม่ซ่อนอยู่หลายรอบ — และนั่นคือเหตุผลที่ระบบจริงต้องมีเพดานว่าจะลองกี่ครั้ง
examples/s02/04_scan_the_room.py↗ — บอร์ดได้ยินทั้งห้องnets = wifi.scan() # คืน list ของ (ssid, rssi, security, channel)
...
nets.sort(key=lambda net: net[1], reverse=True) # เรียงเอง scan() ไม่เรียงให้
...
st = wifi.status()
...
trap.text("status() rssi = " + str(st["rssi"]) + " แต่ของจริง " +
str(top_rssi) + " dBm")
ก่อนจะถามว่า "ต่อไม่ติดเพราะอะไร" ต้องตอบให้ได้ก่อนว่า บอร์ดได้ยินวงนั้นไหม — สองปัญหานี้แก้คนละทางกันสิ้นเชิง "ไม่ได้ยินเลย" ต้องไปคุยกับผู้สอนเรื่องวงของห้อง ส่วน "ได้ยินแต่ต่อไม่ผ่าน" คือเรื่องรหัสผ่านของทีมเอง
ไฟล์นี้สร้างป้ายห้าแถวไว้ครบตั้งแต่ต้น ก่อน จะรู้ว่าเจอกี่วง เพราะถ้าไปสร้างข้างในลูป จำนวน widget จะขึ้นกับจำนวนวงที่บังเอิญเจอในห้องนั้น ห้องที่มีวงเกินงบของคอร์ส 32 ตัว (เพดานเฟิร์มแวร์ 64) จะทำให้โปรแกรมตายกลางคัน ทั้งที่โค้ดไม่ได้ผิด
ตาคุณ อยู่ท้ายไฟล์ เดินถือบอร์ดไปสุดห้องแล้วรันซ้ำ จดชื่อวงเดิมกับตัวเลข dBm ทั้งสองจุด แล้วดูว่าอันดับของมันเลื่อนไหม
examples/s02/02_link_uptime.py↗ — ต่อติดแล้ว กับ ยังต่ออยู่ up = wifi.is_connected() # ถามใหม่ทุกรอบ นี่คือทั้งบทเรียนของไฟล์นี้
now = 1 if up else 0
...
if up: # --- งานของจอ ---
...
link_lbl.text("ต่ออยู่")
...
chart.set_next(s_link, 100)
else:
link_lbl.text("หลุด")
...
chart.set_next(s_link, 0)
...
if now != last: # --- งานของลิ้นชัก ---
last = now
...
lcd.print("<span class=error>" + str(elapsed // 1000) +
" s ลิงก์หลุด</span>")
ไฟล์นี้ไม่มีคำสั่งใหม่เลยสักตัว โครงเป็นของ examples/s01/08_status_screen.py↗ เป๊ะ ๆ เปลี่ยนแค่ต้นทางของค่า ของใหม่คือการเอาสี่โมดูลมาต่อกันบนจอใบเดียว ซึ่งเป็นสิ่งที่นักพัฒนามืออาชีพ 80 คนบอกว่าตัวอย่างโค้ดทั่วโลกขาดไปมากที่สุด
last = -1 แปลว่า "ยังไม่เคยรู้สถานะมาก่อน" รอบแรกจึงนับเป็นการเปลี่ยนเสมอ ตั้งต้นเป็น 1 เมื่อไร ประวัติจะไม่มีบรรทัดแรก
ตาคุณ อยู่ท้ายไฟล์ ระหว่างที่โปรแกรมกำลังเฝ้าดู เดินถือบอร์ดออกไปจนสุดห้องแล้วเดินกลับมา แล้วเปิดลิ้นชักดูว่าได้กี่บรรทัด และหลุดตอนวินาทีที่เท่าไร
บอร์ดพูด 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 แล้วให้บอร์ดหนึ่งตัวรัน
examples/s02/05_value_leaves_the_board.py↗ สักใบ การ์ดของทีมขึ้น = ทั้งสองทางผ่าน · ไม่ขึ้น = ลองตัวสำรอง
bento-aiot/<ทีม>/telemetry (บอร์ดส่งค่า) · bento-aiot/<ทีม>/event (เหตุการณ์ เริ่มใช้คาบ 3) · bento-aiot/<ทีม>/cmd (คำสั่งเข้าบอร์ด) และผู้สอนแจก TEAM เป็น team01 ถึง team19# เครื่องหมายนี้แปลว่าทุกหัวข้อบน broker ข้อความของคนแปลกหน้าทั้งโลกจะไหลเข้ามา และกล่องรับของบอร์ดมีช่องเดียว ใบใหม่ทับใบเก่า · หน้าเว็บของทีมฟัง bento-aiot/team03/# หน้ารวมของผู้สอนฟัง bento-aiot/+/telemetryDEVICE_ID = "bento-aiot-" + TEAM ยาว 17 ตัวอักษร (เฟิร์มแวร์ตัดที่ 31) ส่วนหน้าเว็บสุ่ม web- ตามด้วยเลขสุ่มทุกครั้ง จึงไม่เตะบอร์ดใครก็ส่งเข้า
bento-aiot/team03/cmdได้ ไม่ใช่แค่หน้าเว็บของเรา — นี่คือเหตุผลที่ไฟล์ 06 ไม่เชื่อคนส่งเลยสักบรรทัด และคือเหตุผลที่คาบ 10 กับ 11 ต้องมีเรื่องสิทธิ์และการเข้ารหัส
examples/s02/05_value_leaves_the_board.py↗ — บันไดสามขั้นที่ห้ามสลับ 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 สาธารณะ "ใครก็ตาม" คือทั้งอินเทอร์เน็ต
knob = -1 # -1 = รอบนี้อ่านลูกบิดไม่ได้
az = -99.0 # -99 = อ่านค่าเอียงไม่ได้
try:
s = sensors.snapshot() # ของเดิมจาก examples/s01/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 # อ่านไม่ได้รอบนี้ ไม่ใช่เหตุให้หยุดส่ง ค่าข้างบนบอกให้รู้แล้ว
ไฟล์นี้ส่งค่าจริงสองค่า ลูกบิด (Eva Kit: ลูกบิดสีน้ำเงิน · Dev Kit: VR1 — sensors.pot อ่านตัวนี้ตัวเดียว) กับค่าเอียงแกน z จาก BMI270 ทั้งสองบอร์ดมีทั้งคู่ คนหนึ่งหมุน อีกคนเอียง คนที่นั่งดูอีกฝั่งจึงพิสูจน์ได้ด้วยมือตัวเองว่าขยับที่นี่แล้วเลขที่โน่นขยับตาม ไม่ใช่ตัวเลขสุ่มที่ใครก็เถียงได้
mqtt ของบอร์ดไม่เข้ารหัส และ broker วันนี้เป็นของสาธารณะ ห้ามส่งของที่เป็นความลับ (คาบ 11 ค่อยย้ายไปทางเข้ารหัส) · เฟิร์มแวร์ส่งแบบ retain ไม่ได้ หน้าเว็บที่เปิดช้าเห็นแค่ใบถัดไป ไฟล์จึงส่ง 60 ใบ ใบละ 2 วินาที
ตาคุณ อยู่ท้ายไฟล์ เปิดหน้าเว็บของทีม หมุนลูกบิดหรือเอียงบอร์ด จดว่าเห็นใบแรกที่
nเท่าไร แล้วลองตั้งTEAMชนกับทีมข้าง ๆ ชั่วคราว ดูว่าใครถูกเตะออก
examples/web/my_first_reader.htmlลิงก์ของหน้าอ่านค่า เปลี่ยน team05 ท้ายลิงก์เป็นเลขทีมของคุณ
// ถ้าดาวน์โหลดไฟล์ไปเปิดเอง แก้บรรทัดนี้แทนการต่อท้ายลิงก์
let TEAM = "teamXX"; // ทีมของคุณ ตามที่อาจารย์แจก เช่น "team05"
bento-aiot/<TEAM>/# แล้ววาดหนึ่งกล่องต่อหนึ่งคีย์ของ JSON (knob az n ...) และมีปุ่มส่งเสียงกับเปิดปิดไฟ LED 0 ที่ส่งเข้า .../cmd ให้ไฟล์ 06bento-aiot/+/telemetry กับ bento-aiot/+/event เห็นทุกทีมเป็นการ์ด และส่งคำสั่งเข้า bento-aiot/<ทีม>/cmd ได้-emu ท้าย client_id เอง จึงไม่เตะบอร์ดจริงของทีม · ถ้าต่อไม่ได้ภายใน 5 วินาที มันถอยไปใช้ broker จำลองในตัวและบอกใน Console · กด Run ใหม่หรือรีโหลดหน้าก่อน ถ้าเปิด IDE ค้างไว้ตั้งแต่ก่อน 08:41ไม่มีไฟล์หน้าเว็บของเรา ใช้หน้าทดลองของผู้ให้บริการแทนได้ https://www.hivemq.com/demos/websocket-client/ ตั้ง host
broker.hivemq.comport8884เปิด SSL แล้ว subscribebento-aiot/<ทีมของคุณ>/#
examples/s02/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()
ไฟล์ 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 · วันนี้คนส่งคือปุ่มบนหน้าเว็บ examples/web/my_first_reader.html ซึ่งส่งเข้า bento-aiot/<ทีม>/cmd
ครึ่งหนึ่งของไฟล์นี้คือ การไม่เชื่อคนส่ง เพราะบน broker สาธารณะ ใครที่รู้ชื่อหัวข้อก็ส่งเข้ามาได้ ข้อความจึงมาจากคนที่เราไม่รู้จักได้จริง เขาอาจพิมพ์ผิด อาจส่งของที่ไม่ใช่ JSON หรืออาจสั่งดวงไฟที่บอร์ดนี้ไม่มี ทั้งสามกรณีต้องจบลงที่ข้อความบนจอ ไม่ใช่ที่โปรแกรมตาย · ก่อนจบ ไฟล์นี้ปิดไฟทุกดวงเสมอ ไม่ทิ้งบอร์ดไว้ในสถานะที่คำสั่งสุดท้ายบังเอิญตั้งไว้ — เป็นนิสัยที่ระบบซึ่งต้องแขวนอยู่หน้างานหกเดือนขาดไม่ได้
นี่คือคำตอบของเกมต้นคาบ ทั้งห้องส่งพร้อมกัน แต่บอร์ดถามกล่องทุก 100 ms และกล่องมีช่องเดียว ใบที่มาถึงระหว่างนั้นทับกันเอง · ตาคุณ อยู่ท้ายไฟล์ ตั้ง
POLL_MS = 3000แล้วกดปุ่มบนหน้าเว็บสามครั้งรวดในวินาทีเดียว แล้วนับว่าตัวเลขบนจอขึ้นกี่ใบ เทียบกับสามใบที่ส่งไปจริง
คาบที่แล้วเราเห็นบอร์ดอ่านของจริงและวาดขึ้นจอได้ วันนี้เราเห็นว่าของจริงตัวเดียวกันนั้นเดินทางออกไปได้ไกลแค่ไหน และเดินกลับมาสั่งงานได้ด้วย โดยที่โค้ดฝั่งเรายังเป็นภาษาไพทอนไม่กี่สิบบรรทัดเหมือนเดิม
ไม่มีบรรทัดไหนในสองไฟล์นี้ที่ต้องเขียนเรื่องคลื่นวิทยุ เรื่องโปรโตคอล หรือเรื่องการต่อ TCP เลย เพราะเฟิร์มแวร์ทำให้หมดแล้ว งานที่เหลือของเราคือ ตัดสินใจว่าจะส่งอะไรออกไป และจะยอมรับคำสั่งอะไรกลับมา
อีกสิบคาบข้างหน้าไม่มีอะไรใหญ่กว่าภาพนี้ มีแต่การทำให้มันปลอดภัยขึ้น เร็วขึ้น และฉลาดขึ้น — โครงยังเป็นภาพเดิม
examples/s02/07_platform_in_one_call.py↗ — อ่านนอกเวลา วันนี้ยังต่อไม่ได้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 แบบไม่เข้ารหัส ไฟล์นี้จึงเป็นการอ่านนอกเวลาที่ชี้ไปคาบ 10 กับ 11 ถ้ารันวันนี้ มันแสดงคลังค่าตั้งแล้วจบ ไม่เขียนอะไรลงแฟลช
สิ่งที่มันสอนยังใช้ได้: ไฟล์ 05 กับ 06 ต้องพิมพ์ชื่อ broker ลงในโค้ดตรง ๆ ส่วนคลังค่าตั้งนี้ ตั้งครั้งเดียว เก็บบนแฟลชของบอร์ด ถอดไฟแล้วยังอยู่ โปรแกรมตัวถัดไปถาม config() เอาได้เลย
tesaiot.connect() ต่างจาก wifi.connect() ตรงที่มัน ไม่บล็อก ค่าที่คืนมาแปลว่า "รับคำสั่งไปแล้ว" ไม่ได้แปลว่า "ต่อได้แล้ว" ใครเขียน if tesaiot.connect(): แล้วส่งต่อทันทีจะได้ OSError เพราะสายยังไม่ขึ้น WAIT_MS คือกำหนดเวลาเลิกรอ ซึ่งแยกโปรแกรมที่ล้มเหลวอย่างสุภาพ ออกจากโปรแกรมที่ค้างจนคนดูต้องถอดไฟ
ตาคุณ อยู่ท้ายไฟล์ วันนี้จดค่า
portกับtls_modeที่จอแสดง แล้วตอบว่าทำไมportที่เห็นอาจไม่ใช่พอร์ตที่connect()ใช้จริง · วันที่ได้ชื่อ broker ของแพลตฟอร์มในคาบ 10 ค่อยทำข้อถอดสาย USB แล้วดูว่าค่ายังอยู่ไหม
examples/s02/03_your_link_rule.py↗ — กฎที่คุณเขียนเอง บอร์ดตรวจให้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 แล้วดูว่าเราพลาดอะไรไป
ต้องทำในคาบ · เปิดตามลำดับนี้ ทั้งชุดราว 135 นาที โดยสไลด์แทรกอยู่ระหว่างการรันแต่ละไฟล์
| ลำดับ · เวลา | ไฟล์ | ลงมือทำอะไร แล้วจะเข้าใจอะไร |
|---|---|---|
| 1 · 25 นาที | examples/s02/01_wifi_first_connect.py↗ |
ต่อเน็ตให้ได้ จับเวลาทั้งรหัสถูกและรหัสผิด · รู้ว่าจอที่นิ่งไม่ได้แปลว่าแฮงก์ และ "0.0.0.0" หลอกเราได้ |
| 2 · 15 นาที | examples/s02/04_scan_the_room.py↗ |
ให้บอร์ดฟังคลื่นทั้งห้อง · แยก "ไม่ได้ยินวงนั้นเลย" ออกจาก "ได้ยินแต่รหัสผิด" ได้ด้วยตัวเอง |
| 3 · 25 นาที | examples/s02/02_link_uptime.py↗ |
ประกอบสี่โมดูลเป็นจอเฝ้าลิงก์หนึ่งใบ แล้วเดินถือบอร์ดออกไปให้มันหลุดจริง |
| 4 · 25 นาที | examples/s02/05_value_leaves_the_board.py↗ |
ส่งค่าลูกบิดจริงออกจากบอร์ด · เห็นบันไดสามขั้นและรู้ว่าตัวเองไปหยุดที่ขั้นไหน |
| 5 · 25 นาที | examples/s02/06_command_comes_back.py↗ |
รับคำสั่งจากที่ไกลมาสั่งไฟบนบอร์ด · เข้าใจว่ากล่องรับมีช่องเดียวแปลว่าอะไร |
| 6 · 20 นาที | examples/s02/03_your_link_rule.py↗ |
เขียนกฎ is_usable() เองจนเลขสองตัวบนจอตรงกัน · "ต่ออยู่" ไม่ได้แปลว่า "ส่งข้อมูลออกได้" |
| อ่านนอกเวลา | examples/s02/07_platform_in_one_call.py↗ |
คลังค่าตั้งบนแฟลช · วันนี้อ่านอย่างเดียว tesaiot.connect() เป็น TLS เสมอ ต่อจริงคาบ 10-11 |
ทุกไฟล์จบด้วยบล็อก ตาคุณ — อ่านแล้วรันแล้วยังไม่จบ ต้องแก้แล้วรันซ้ำถึงจะจบ
สิ่งที่เฟิร์มแวร์ทำให้แล้ว (70%)
คุมชิปวิทยุ · จับมือกับเราเตอร์ · ขอและต่ออายุเลข IP · ต่อ TCP ไปหา broker · พูดโปรโตคอล MQTT ทั้ง publish และ subscribe · ลองต่อใหม่เองเมื่อหลุด · ซิงก์นาฬิกาจากอินเทอร์เน็ต · วาดไอคอนบนแถบบนสุด
สิ่งที่เป็นงานของเรา (30%)
ตัดสินใจว่าจะส่ง ค่าอะไร ออกไป และในรูปแบบไหน · ยอมรับคำสั่งอะไรบ้าง และจะทำอย่างไรกับคำสั่งที่พิมพ์มาผิด · นิยามว่า "ลิงก์ใช้ได้" แปลว่าอะไรสำหรับงานของเรา · แล้วรายงานออกมาให้คนที่เดินผ่านหน้าจออ่านรู้เรื่องภายในสายตาเดียว

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

examples/s02/ แก้ WIFI_SSID กับ WIFI_PASS ให้ตรงกับ Hotspot มือถือของทีม และในไฟล์ 05 กับ 06 แก้ TEAM จาก teamXX เป็นเลขทีมที่ผู้สอนแจก ถ้าลืม ไฟล์จะหยุดตั้งแต่ขั้นแรกพร้อมบอกบนจอ เพราะชื่อทีมซ้ำจะเตะบอร์ดของทีมอื่นหลุด (BROKER ตั้งไว้แล้ว ไม่ต้องแก้) แล้วกด Program to Device
พื้นที่วาดของเรายังเป็น 792 x 398 พิกเซล เท่าเดิม งบ widget ยังเป็น งบของคอร์ส 32 ตัว (เพดานเฟิร์มแวร์ 64) และมุมขวาล่างราว 100x58 ยังเป็นของปุ่ม Console ที่เฟิร์มแวร์จองไว้
ข้อควรระวังเรื่องเครือข่ายของห้อง: เครือข่ายที่ต้อง login หรือกดยอมรับเงื่อนไขบนหน้าเว็บก่อน เช่น WiFi ของคณะ จะทำให้ connect() คืน True และได้เลข IP ตามปกติ แต่นาฬิกาไม่ขึ้น และ mqtt.connect() จะไปไม่ถึง broker — นั่นคือกรณี "ติดข้อแรก ไม่ผ่านข้อสอง" · เน็ตที่กันพอร์ต 1883 ขาออกก็ไปไม่ถึงเช่นกัน
คาบที่แล้วเราเห็นแล้วว่า CM33 รันโค้ดของเรา ส่วน CM55 วาดจอ วันนี้มีข้อเท็จจริงเพิ่มมาข้อเดียว: ทั้งวิทยุ WiFi และงาน MQTT อยู่ฝั่ง CM33 เหมือนกัน
นั่นอธิบายทุกอย่างที่เราเจอวันนี้ — wifi.connect() บล็อกได้ทั้งโปรแกรม เพราะมันบล็อกคอร์เดียวกับที่รัน Python ของเรา และระหว่างที่มันบล็อก ไม่มีใครเหลือไปเคาะกล่องจดหมายให้ CM55 วาดอะไรใหม่ ส่วน mqtt.get_message() ตรงกันข้าม มันไม่บล็อกเลย จึงต้องเป็นเราที่วนถามเอง
ถ้าเข้าใจสไลด์นี้ จะไม่มีวันเขียนป้ายบอกสถานะไว้หลังบรรทัดที่บล็อกอีกเลย
ทีมต่อบอร์ดเข้าเครือข่ายด้วย wifi.connect() แล้วรายงานหมายเลข IP ขึ้นจอสำเร็จ
แปลเป็นสิ่งที่ตรวจได้จริง:
WIFI_SSID และ WIFI_PASS ให้ตรงกับ Hotspot มือถือของทีมได้เองexamples/s02/01_wifi_first_connect.py↗ ขึ้นเลข IP ที่ ไม่ใช่ 0.0.0.0 บนจอconnect() ใช้ทั้งกรณีรหัสถูกและรหัสผิด พร้อมอธิบายว่าทำไมต่างกันexamples/s02/04_scan_the_room.py↗ ขึ้นรายชื่อวงในห้อง และชี้ได้ว่าเลข status() กับเลขจาก scan() ต่างกันตรงไหนexamples/s02/02_link_uptime.py↗ เดินครบ 30 วินาที ทำให้ลิงก์หลุดจริงหนึ่งครั้ง แล้วชี้ได้ว่าหลุดวินาทีที่เท่าไรexamples/s02/05_value_leaves_the_board.py↗ รันแล้วเห็นค่าของทีมตัวเองบนหน้าเว็บ examples/web/my_first_reader.html — หรือถ้าเน็ตของห้องกันไว้ อ่านจากจอได้ว่าสายขาดที่ขั้นไหนและเพราะอะไรexamples/s02/06_command_comes_back.py↗ รันแล้วบอกได้ว่าบอร์ดกำลังฟังหัวข้ออะไร และคำสั่งที่มันรู้จักมีอะไรบ้างexamples/s02/03_your_link_rule.py↗ รายงานครบ 2 ครั้ง (เลขสองตัวบนจอตรงกัน)ข้อที่ทีมส่วนใหญ่ตกคือข้อที่ห้า เพราะต้องออกแรงทำให้มันพังจริง · ข้อ 05 กับ 06 ผ่านเมื่อเห็นค่าของทีมบนหน้าเว็บ หรือถ้าเน็ตของมหาวิทยาลัยกันพอร์ตไว้ทั้งห้อง ทีมชี้จากจอได้ว่าสายขาดที่ขั้นไหน
| อาการ | สาเหตุที่แท้จริง | วิธีแก้ |
|---|---|---|
| จอนิ่งค้างนาน คิดว่าบอร์ดแฮงก์ | wifi.connect() กำลังทำงาน มันบล็อกได้ถึงราว 85 วินาที |
รอให้จบ อย่ากดรีเซ็ต และคราวหน้าใส่ป้าย + ui.poll() ไว้ก่อนบรรทัดนั้น |
| จอว่างเปล่าตลอดช่วงที่รอ ทั้งที่เขียนป้ายไว้แล้ว | ลืมเคาะ ui.poll() ก่อนเข้าบรรทัดที่บล็อก |
ย้าย ui.poll() ขึ้นมาไว้ก่อน wifi.connect() และก่อน wifi.scan() |
| โค้ดบอกว่าได้ IP แล้ว แต่ส่งอะไรก็ไม่ออก | เขียน if wifi.ip(): — "0.0.0.0" เป็นสตริงที่ไม่ว่าง จึงถือว่าจริง |
เทียบตรง ๆ if wifi.ip() == "0.0.0.0": |
connect() คืน False ทุกครั้ง |
SSID หรือรหัสผ่านผิด หรือบอร์ดไม่ได้ยินวงนั้นเลย | เปิด examples/s02/04_scan_the_room.py↗ ก่อน แล้วดูว่าชื่อวงโผล่ในรายการไหม |
| กราฟความแรงสัญญาณเป็นเส้นศูนย์ตลอด | หยิบ wifi.status()["rssi"] มาใช้ ซึ่งเป็นค่าตายตัว |
ความแรงจริงมาจาก wifi.scan() เท่านั้น |
mqtt.connect() ได้ TypeError |
ใส่ชื่ออาร์กิวเมนต์เป็น user= |
ชื่อจริงคือ username= |
| ส่งไปได้สองสามใบแล้วโปรแกรมตาย | publish() ตอนสายหลุดไม่คืน False มันโยน OSError |
ครอบด้วย try / except OSError ทุกครั้ง ไม่ใช่เช็กแค่ค่าที่คืน |
| ยิงคำสั่งมาสามใบ แต่บอร์ดนับได้ใบเดียว | กล่องรับของ get_message() มีช่องเดียว ใบใหม่ทับใบเก่า |
ลด POLL_MS ให้ถามถี่ขึ้น และห้ามหลับยาวในลูป |
| สองบอร์ดผลัดกันหลุดทั้งคาบ | client_id ซ้ำกัน broker เตะตัวเก่าออกทุกครั้งที่ตัวใหม่เข้ามา บน broker สาธารณะชนได้กับทุกคนในโลก |
ใช้ TEAM ที่ผู้สอนแจก ไม่ซ้ำกับทีมอื่น และคง "bento-aiot-" ไว้หน้า DEVICE_ID |
| บอร์ดส่งครบแต่หน้าเว็บว่าง | TEAM ในหน้าเว็บกับในไฟล์ไม่ตรงกัน หรือเปิดหน้าเว็บหลังบอร์ดส่งจบแล้ว (เฟิร์มแวร์ส่ง retain ไม่ได้) |
ตรวจ TEAM สองที่ให้ตรงกัน แล้วรันไฟล์ 05 ใหม่ขณะหน้าเว็บเปิดอยู่ |
| หน้าเว็บขึ้นว่าผิดพลาดหรือต่อไม่ได้ | เน็ตกันพอร์ต 8884 ของเบราว์เซอร์ | ลองตัวสำรองที่ผู้สอนประกาศ wss://test.mosquitto.org:8081/mqtt หรือต่อฮอตสปอตมือถือ |
ตั้ง tesaiot.config_set("port", "1883") แล้วไม่มีผล |
tesaiot.connect() เลือกพอร์ตจากโหมด TLS เอง (8883/8884) ไม่อ่านคีย์ port |
ใช้โมดูล mqtt กับ broker 1883 ส่วน tesaiot รอคาบ 10 กับ 11 |
seg.value(4218) ขึ้น 4218 แต่ไม่ได้ 4218.0 |
.value() ส่งได้แต่จำนวนเต็ม |
ใช้ seg.text("4218.0") เมื่อต้องการทศนิยม |
| ข้อความไทยยาว ๆ ขาดหายท้ายบรรทัด | เกินเพดาน — lcd.print() 127 ไบต์ ป้าย ui 126 ไบต์ ไทยตัวละ 3 ไบต์ |
แบ่งเป็นสองบรรทัด หรือใช้ท่าของ examples/s01/06_safe_print.py↗ |
ครึ่งหนึ่งของตารางนี้ ไม่มี error ให้จับสักตัว โปรแกรมเดินผ่านไปเงียบ ๆ แล้วรายงานสิ่งที่ไม่จริง นั่นคือประเภทของบั๊กที่แพงที่สุดในงานเครือข่าย
คาบนี้ไม่มีไฟล์ฝึกแยกต่างหาก งานของทีมคือไฟล์ตัวอย่างของคาบนี้เอง ทำตามลำดับในตารางแล้วทำบล็อก "ตาคุณ" ท้ายไฟล์ให้ครบทุกไฟล์
ทำสี่ขั้น: หนึ่ง ขึ้นวงให้ได้และรู้ว่าบอร์ดได้ยินอะไรบ้าง สอง เฝ้าลิงก์แล้วออกแรงทำให้มันหลุดจริง สาม ส่งค่าออกไปแล้วรับคำสั่งกลับมา สี่ เขียนกฎ is_usable() ของทีมเองจนบอร์ดบอกว่าผ่าน
ทีมที่เสร็จก่อน ลองรัน 05 กับ 06 พร้อมกันสองบอร์ดในทีมข้าง ๆ โดยให้บอร์ดหนึ่งส่งค่าลูกบิด แล้วอีกบอร์ดหนึ่งเป็นคนสั่งไฟกลับมา สองทีมจะได้เห็นว่าระบบที่มีอุปกรณ์มากกว่าหนึ่งตัวหน้าตาเป็นอย่างไร
อย่าแก้หลายไฟล์พร้อมกันแล้วค่อยรันทีเดียว — ไฟล์ละครั้ง รันครั้ง จะรู้ทันทีว่าพังที่ไหน
ทุกบอร์ดรัน examples/s02/06_command_comes_back.py↗ ด้วย TEAM ของตัวเอง ไฟล์ 06 ฟังนาน 15 นาที (LISTEN_MS = 900000) พอสำหรับสองรอบ ถ้ารันค้างไว้นานกว่านั้นก่อนเกม ให้กดรันใหม่ก่อนเริ่ม
say ไปที่ team01 จากหน้ารวม https://advance-innovation-centre-aic.github.io/embedded-systems-for-aiot-developer/examples/web/mqtt_dashboard.htmlteam00 บนโต๊ะผู้สอน นาฬิกาหยุดเมื่อคำขึ้นจอหน้าห้อง · รอบสองแข่งกับเวลารอบแรกของห้องเราเอง| คนนั่งบอร์ด | ศูนย์ควบคุม | คนจด | คนแก้ |
|---|---|---|---|
| ดูจอ อ่านคำออกเสียง | พิมพ์แล้วส่งต่อ | เวลาของห้องทั้งสองรอบ | คำไม่ขึ้น ดูลิ้นชัก Console ว่ามาถึงไหม |
ถ้ามีคำที่ไม่มีใครในห้องส่งโผล่ขึ้นมา นั่นคือ broker สาธารณะทำงานตามที่มันเป็น ใครก็เขียนเข้ามาได้ · ห้องที่มีไม่ถึงสิบเก้าทีม ทีมสุดท้ายคือทีมที่เลขสูงสุด
ฝั่งเครือข่าย
การเข้าร่วมเครือข่ายเป็นกระบวนการหลายจังหวะ ไม่ใช่คำสั่งเดียวจบ · การได้เลขที่อยู่เป็นคนละขั้นกับการเข้าร่วมได้ · การส่งข้อความผ่านคนกลางที่เรียกว่า broker ทำให้ผู้ส่งกับผู้รับไม่ต้องรู้จักกัน · ความล้มเหลวใช้เวลามากกว่าความสำเร็จเสมอ เพราะมีการลองใหม่ซ่อนอยู่
ฝั่ง Python และวิทยาการคอมพิวเตอร์
dict กับการแปลงเป็น JSON · ความต่างระหว่าง bytes กับ str และเหตุที่ต้อง .decode() · try/except กับความล้มเหลวที่คาดไว้แล้ว · การเทียบสตริงตรง ๆ แทนการพึ่ง truthiness · การจับเวลาคร่อมคำสั่งที่เราควบคุมไม่ได้
ฝั่งการออกแบบระบบ
โปรแกรมที่ล้มเหลวต้องบอกให้ได้ว่าล้มที่ขั้นไหน · ข้อมูลที่มาจากคนอื่นต้องตรวจก่อนใช้เสมอ · การรอหลักฐานหลายรอบก่อนรายงาน · การใช้ข้อมูลที่บันทึกไว้แล้วเป็นชุดทดสอบ แทนการหวังพึ่งสภาพจริงที่ควบคุมไม่ได้
ข้อสุดท้ายคือของที่ทีมทดสอบซอฟต์แวร์ทั่วโลกใช้ทุกวัน และวันนี้น้อง ๆ ได้ใช้มันไปแล้วโดยไม่รู้ตัว
วันนี้เราได้:
พาบอร์ดขึ้นเครือข่ายด้วยโค้ดของเราเอง · ให้บอร์ดสำรวจคลื่นทั้งห้องแล้ววินิจฉัยตัวเองได้ · แยก "ต่อติดตอนนั้น" ออกจาก "ยังต่ออยู่ตอนนี้" · ส่งค่าจริงออกไปให้เครื่องอื่นเห็น และรับคำสั่งจากที่ไกลกลับมาสั่งของบนบอร์ด · เขียนกฎเองว่าลิงก์แบบไหนเรียกว่าใช้ได้
การบ้านของทีม: เลือกทำ 1 ข้อจากสี่ข้อในสไลด์ถัดไป บันทึกลง worksheet
คาบถัดไปเราจะให้ปุ่มกับไฟขึ้น broker ด้วย ชื่อชุดเดิมทั้งหมด broker.hivemq.com · TEAM · bento-aiot/<ทีม>/... ไฟล์ examples/s03/07_button_to_broker.py↗ ส่งการกดปุ่มเป็น event และสถานะไฟใน telemetry แล้วรับ cmd จากหน้าเว็บเดิมมาเปิดปิดไฟ เก็บหน้าเว็บของทีมไว้ ไม่ต้องปิด
คาบหน้า: สองคาบแรกเราเล่นของที่ไลบรารีมีให้ครบแล้ว ตั้งแต่คาบ 3 เราจะ สร้างมันขึ้นมาเองทีละชิ้น เริ่มที่หลอด LED ทุกดวงบนบอร์ด (gpio.num_leds() บอกว่ากี่ดวง — Eva 3 · Dev Kit 5) กับปุ่มผู้ใช้จริงหนึ่งปุ่ม ผ่านโมดูล gpio ที่วันนี้เพิ่งได้เห็นผ่านตาในไฟล์ 06
โครงลูป "อ่าน → ตัดสิน → รายงาน" ที่ใช้มาสองคาบแล้ว จะเป็นโครงเดียวกับคาบหน้าเป๊ะ ๆ เปลี่ยนแค่ว่าปลายทางของการตัดสินคือหลอดไฟ ไม่ใช่ตัวหนังสือ
สองคาบแรกจบแล้ว น้อง ๆ ได้เห็นของทั้งชุดที่ไลบรารีนี้มีให้ ตั้งแต่จอ เซนเซอร์ เสียง ไปจนถึงการส่งข้อมูลออกไปให้คนที่อยู่คนละที่ ไม่ใช่เพื่อให้จำได้หมด แต่เพื่อให้รู้ว่าปลายทางหน้าตาเป็นอย่างไร ตั้งแต่คาบหน้าเราจะย้อนกลับไปสร้างมันทีละชิ้นด้วยมือตัวเอง
คำถามคิดต่อ: ถ้าบอร์ดของทีมต้องแขวนอยู่ในโรงงานหกเดือน แล้วส่งค่าออกไปทุกนาที · จะรู้ได้อย่างไรว่ามันหยุดส่งตอนตีสาม · และคำสั่งที่ส่งกลับมาได้ ควรมีใครสั่งได้บ้าง
ข้อ 1 · จอหน้าประตู
ดัดแปลง examples/s02/02_link_uptime.py↗ ให้เหลือข้อมูลน้อยที่สุดเท่าที่จะยังตอบคำถาม "ตอนนี้บอร์ดออนไลน์อยู่ไหม" ได้จากระยะสามเมตร ตัดอะไรออกไปบ้าง แล้วทำไมถึงกล้าตัดสิ่งนั้น
ข้อ 2 · ส่งของทีมเอง
แก้ payload ใน examples/s02/05_value_leaves_the_board.py↗ ให้ส่งค่าที่ทีมเลือกเองเพิ่มอีกอย่างจาก sensors.snapshot() แล้วเปิดหน้าเว็บ examples/web/my_first_reader.html ดูว่าฝั่งรับเห็นคีย์ใหม่เป็นกล่องใหม่จริงไหม และคีย์นั้นควรตั้งชื่อว่าอะไรคนอื่นถึงจะเข้าใจ
ข้อ 3 · คำสั่งของทีมเอง
เพิ่มคำสั่งที่สี่ให้ examples/s02/06_command_comes_back.py↗ นอกจาก beep led say ที่มีอยู่ แล้วอธิบายว่าถ้าคนส่งพิมพ์คำสั่งนั้นมาผิดรูปแบบ โปรแกรมของทีมจะทำอย่างไร
ข้อ 4 · เทปของทีมเอง
จดผลของ wifi.is_connected() กับ wifi.ip() จากการเดินจริงของทีม ลงเป็น TAPE ชุดใหม่ใน examples/s02/03_your_link_rule.py↗ แล้วตั้ง WANT_REPORTS ให้ตรงกับที่ควรจะเป็น พร้อมอธิบายว่านับมาได้อย่างไร
เขียนคำตอบลง worksheet แล้วเอามาเล่าให้เพื่อนฟังต้นคาบหน้า


bento/eva-team03/... ไฟล์ปัจจุบันใช้ broker.hivemq.com กับ bento-aiot/team03/telemetry รอถ่ายใหม่) · 06 คนอื่นพิมพ์คำสั่งจากที่ไกล แล้วไฟบนโต๊ะเราติด (ภาพเก่า ไฟล์ปัจจุบันฟัง bento-aiot/team03/cmd รอถ่ายใหม่)
PLATFORM_BROKER รอถ่ายใหม่)เอกสารของผู้ผลิต
มาตรฐานและเอกสารเปิด
connect() ถึงกินเวลาหลายวินาที และทำไมความล้มเหลวถึงกินเวลามากกว่า"0.0.0.0"json.dumps() กับ json.loads() ผลิตและอ่านbroker.hivemq.com (1883 TCP · 8884 wss) ไม่มีรหัสผ่าน https://www.hivemq.com/demos/websocket-client/ · หน้าเว็บ examples/web/ ใช้ MQTT.js 5.16.0งานวิจัยที่กำหนดรูปร่างของคาบนี้
wifi มีอะไรให้ใช้บ้าง" จึงมีแค่ห้าช่อง ไม่ใช่สารบัญของทั้งไลบรารีหมายเหตุเรื่องภาพ
ไดอะแกรม SVG ทุกภาพในเด็คนี้วาดขึ้นใหม่สำหรับหลักสูตรนี้ · ภาพลำดับการเข้าร่วมเครือข่ายมาจาก Wikimedia Commons ระบุที่มาไว้ใต้ภาพ · ภาพหน้าจอของ 04_scan_the_room.py เป็นภาพจริงจากการรันที่ความละเอียดเท่าจอของทั้งสองบอร์ด · ภาพหน้า Playground เป็นภาพถ่ายจากบอร์ด Eva Kit จริง บันทึกโดยผู้สอน
ข้อเท็จจริงเกี่ยวกับพฤติกรรมของโมดูล wifi mqtt tesaiot sensors gpio lcd และ ui ตรวจสอบจากซอร์สโค้ดของโปรเจกต์ KIT_PSE84_EVAL_EPC2-MicroPython-BentoClaw (Eva Kit) TESAIoT_KIT_PSE84_AI-Micropython-BentoClaw (Dev Kit) และ BENTO-TESAIoT-libraries โดยตรง
ตัวเลข 85 วินาทีคือเพดานที่วัดได้จริงบนบอร์ดตอนใส่รหัสผิด ไม่ใช่ค่าจากเอกสาร ทีมที่วัดได้ต่างจากนี้ บอกผู้สอนได้เลย
fit-css
VIDEO-SLOT: คลิป 60-90 วินาที ถ่ายสองจอพร้อมกันในเฟรมเดียว — ซ้ายคือจอบอร์ด (Eva Kit หรือ Dev Kit) ที่รัน examples/s02/05_value_leaves_the_board.py ขวาคือเบราว์เซอร์ที่เปิด examples/web/my_first_reader.html ของทีม team03 (ฟัง bento-aiot/team03/# ผ่าน broker.hivemq.com) → มือหมุนลูกบิดหรือเอียงบอร์ด แล้วให้เห็นเลข knob หรือ az บนหน้าเว็บขยับตามมือ → ตัดไปที่ examples/s02/06_command_comes_back.py กดปุ่มเปิดไฟบนหน้าเว็บ (ส่ง {"cmd":"led","n":<ดัชนีดวงที่มองเห็นบนบอร์ดนั้น>,"on":1}) แล้วถ่ายให้เห็นหลอด LED บนบอร์ดติดขึ้นมา
ผู้สอน: ครึ่งหนึ่งของโมดูลนี้เป็นเรื่องชิปนิรภัย OPTIGA — ทั้งสองบอร์ดคอมไพล์โมดูล `optiga` มาเหมือนกัน ที่ต่างคือสวิตช์บิลด์ `ENABLE_OPTIGA_CLM` ซึ่ง Dev Kit เปิดเป็นค่าปริยาย ส่วน Eva Kit ปิดไว้ และ Makefile ของ Eva เขียนกำกับว่ายังไม่ได้พิสูจน์บนฮาร์ดแวร์ ไฟล์นี้จึงไม่แตะฝั่งนั้นเลยสักตัวไม่ว่าบอร์ดไหน เรื่องความปลอดภัยของจริงเป็นงานของคาบ 11
ทีมละหนึ่งบอร์ด สลับกันเป็นคนพิมพ์ทุกช่วง คนที่ไม่ได้พิมพ์รับหน้าที่จับเวลาและจดตัวเลข
VIDEO-SLOT: คลิป 20-30 วินาที ถ่ายจอบอร์ดตอนผ่าน MVP — เริ่มที่ examples/s02/01_wifi_first_connect.py ขึ้นเลข IP จริงตัวใหญ่ แล้วตัดไปที่ examples/s02/05_value_leaves_the_board.py ที่ป้ายสามขั้นไล่เป็นเขียวครบ และเลขใบที่ส่งเดินขึ้น ใช้เป็น "หน้าตาของคำว่าผ่าน" ให้ทุกทีมเทียบ