ลงมือทำ: ไฟวิ่งกับปุ่ม แล้วส่งขึ้น broker
โมดูล 2 — จากจอสู่ฮาร์ดแวร์ · สไลด์: slides.md · ภาพรวมโมดูล · หน้าหลักสูตร
เติมช่องว่างหกจุดจนได้ไฟวิ่งครบทุกดวงที่ปรับจังหวะได้กับปุ่มที่นับตรงสิบครั้งสิบ แล้วต่อยอดให้การกดปุ่มหนึ่งครั้งไปโผล่บนหน้าเว็บผ่าน broker โดยเขียนรอบข้อจำกัดจริงของเฟิร์มแวร์
เป้าหมาย
หัวข้อที่มีชื่อว่า “เป้าหมาย”เมื่อจบบทเรียนนี้ คุณจะ:
- เติมช่องว่างหกจุดใน practice/s03_led_button.py จนผ่าน MVP คือไฟวิ่งครบทุกดวงที่ gpio.num_leds() บอก แก้ STEP_MS อย่างน้อยสองค่าแล้วจังหวะเปลี่ยนตาม กดปุ่มผู้ใช้สิบครั้งตัวเลขขึ้นสิบพอดี และจบโปรแกรมด้วยไฟดับทุกดวงพร้อมบรรทัดสรุปสีเขียว
- จับคู่อาการที่เจอบ่อยกับสาเหตุและวิธีแก้ได้ (เลขขึ้นทีละ 3–5 ขึ้นทีละ 2 ไม่ขึ้นเลย ไฟค้างดวงเดียว ไฟติดสองดวง) และอธิบายให้ผู้สอนฟังได้ว่าถ้าเอาโค้ดกันเด้งออกจะเกิดอะไรและเพราะอะไร
- รัน 07_button_to_broker.py ด้วยชื่อทีมของตัวเองจนหน้า my_first_reader.html เห็น telemetry ทุก 2 วินาทีและ event ทันทีที่กด แล้วอธิบายได้ว่า retain ที่เป็น false เสมอ กล่องรับช่องเดียว OSError ตอนสายหลุด และ client_id ที่ซ้ำไม่ได้ ทำให้โค้ดต้องหน้าตาแบบนี้อย่างไร
- อธิบายได้ว่าทำไมเกมกดเร็วทั้งห้องจับเวลาบนบอร์ดแทนลำดับที่ผลมาถึง และทำไมจดเวลาตอนขาเริ่มเปลี่ยนแทนตอนที่กันเด้งยืนยัน
ก่อนเริ่ม
หัวข้อที่มีชื่อว่า “ก่อนเริ่ม”ต้องผ่านบทเรียน 2.2 มาแล้ว เพราะบทเรียนนี้ประกอบห้าท่าของไฟล์เฉลยที่แกะในบทเรียนนั้นให้เป็นโปรแกรมของทีม
ส่วน broker ใช้ของชุดเดิมจากบทเรียน 1.4–1.6 คือ Hotspot มือถือของทีม (ชื่อกับรหัส) และรหัส TEAM ที่ไม่ซ้ำใครชุดเดิม เช่น nok4821 (ถ้าเรียนเป็นกลุ่ม ใช้เลขทีมที่ผู้จัดแจก)
บนจอบอร์ดแตะการ์ด BENTO Playground ค้างไว้ และเตรียมกล้องมือถือไว้ถ่ายไฟกับจอเก็บลงบันทึกการเรียน
- อุปกรณ์: บอร์ด Eva Kit หรือ TESAIoT Dev Kit ที่ลงเฟิร์มแวร์ MicroPython ของ BENTO แล้ว หรือ BENTO Emulator ใน BENTO IDE (07_button_to_broker.py บน Emulator ส่งถึง broker จริงได้แล้ว โดยเติม -emu ท้าย client_id จึงไม่เตะบอร์ดของทีม ส่วน 08_class_race.py ยังไม่เคยลองบน Emulator และไม่ได้ยินคำสั่งจากหน้าห้อง)
- เรียนมาก่อน: บทเรียน 2.2 — หลังไฟและปุ่ม: active-low กันเด้ง และลูปที่ไม่หยุด
MVP ของชุดบทเรียน 2.1–2.3 คือไฟวิ่งครบทุกดวงของบอร์ดที่ปรับจังหวะได้ กับปุ่มผู้ใช้ (เรียกด้วยชื่อจาก .name())
ที่นับครั้งขึ้นจอ ข้อที่ยากที่สุดไม่ใช่การทำให้ได้ แต่คือ อธิบายได้ ว่าถ้าเอาโค้ดกันเด้งออกจะเกิดอะไร
และสังเกตว่ากับดักสามข้อแรกในตารางของสไลด์เป็นเรื่อง ตรรกะเวลา ไม่ใช่ไวยากรณ์ ซึ่งเป็นลักษณะเฉพาะของงานฝังตัว
ไฟล์เฉลยวางค่าที่ปรับบ่อยสี่ตัวไว้บนสุดเป็น หน้าปัดของโปรแกรม (STEP_MS DEBOUNCE_MS POLL_MS RUN_MS)
เกณฑ์ “ปรับจังหวะได้” จึงเหลือแค่แก้ STEP_MS ตัวเดียว ในลูปมีสี่จังหวะที่ไม่เท่ากัน: ปุ่มถูกถามทุก 5 ms ไฟขยับทุก 150 ms
จอเดินทุก UI_MS = 100 ms (เรียก ui.poll() ทุกรอบลูปคือการยิง IPC ข้ามคอร์ 200 ครั้งต่อวินาทีเพื่อรอนิ้วที่มาวินาทีละครั้ง)
และตัวเลขที่คนต้องอ่านเขียนใหม่ไม่เกินวินาทีละครั้ง ทั้งหมดใช้ now = time.ticks_ms() ค่าเดียวต่อรอบ
และ time.sleep_ms(POLL_MS) ท้ายลูปคือจุดเดียวที่โปรแกรมยอมพัก แผงบนจอสร้างครั้งเดียวก่อนเข้าลูป
ปุ่ม “เดินไฟวิ่ง” กับ “หยุดไฟวิ่ง” แยกกันโดยตั้งใจ และกล่องยืนยันถูกสร้างแล้วซ่อนไว้ตั้งแต่ต้น
ห้าท่าเรียงแบบ bring-up ทีละชั้น: ถามบอร์ด → ดับให้หมด → ไฟวิ่ง (งานที่ไม่พึ่งอินพุตจากใคร ถ้าวิ่งได้แปลว่าโครงลูปกับนาฬิกาถูก) → ปุ่ม (ถ้าพังตอนนี้ รู้แน่ว่าพังที่ปุ่มหรือกันเด้ง) → ปิดท้าย การเติมช่องว่างก็ทำตามลำดับเดียวกัน
ต่อขึ้น broker ได้ด้วยการเพิ่มของแค่สามจุดในลูปเดิม: send() สองจุดกับ get_message() หนึ่งจุด ทีมละสามหัวข้อ
bento-aiot/<ทีม>/event (ทันทีที่กด ผ่านกันเด้งแล้ว) …/telemetry (ทุก 2 วินาที) และ …/cmd (เว็บสั่งบอร์ด)
ทุกใบมี n ที่เดินต่อกัน อีกฝั่งเห็นเลขกระโดดก็รู้ว่ามีใบหาย ข้อจำกัดของเฟิร์มแวร์สี่ข้อคือข้อมูลออกแบบ ไม่ใช่ข้อตำหนิ:
publish() ส่ง retain = false เสมอ จึงต้องส่ง telemetry ซ้ำทุก 2 วินาที · get_message() เป็นกล่องช่องเดียว ใบใหม่ทับใบเก่า
จึงต้องหยิบทุกรอบลูป · publish() ตอนสายหลุดโยน OSError ซึ่ง send() ดักไว้แล้วคืน False ให้ลูปหยุดอย่างสุภาพ
· ไม่ต่อใหม่ให้เองและ clean_session เป็นจริงเสมอ จึงถาม is_connected() ทุกรอบ และ client_id ซ้ำกันไม่ได้
ใครต่อทีหลังจะเตะคนก่อนหลุด ไฟล์ 07 จึงรันได้บอร์ดเดียวต่อทีม
ตัวอย่างสมบูรณ์
หัวข้อที่มีชื่อว่า “ตัวอย่างสมบูรณ์”07_button_to_broker.pyแก้สี่บรรทัดบนสุด (WIFI_SSIDWIFI_PASSBROKERTEAM) ห้ามแก้ROOTกับDEVICE_IDถ้าไม่แก้TEAMจากteamXXโปรแกรมไม่ยอมรัน เปิดหน้าmy_first_reader.htmlด้วยลิงก์ในสไลด์ที่ต่อท้าย?team=ด้วยรหัสเดียวกับTEAMรอให้บรรทัดสถานะขึ้นว่าต่อแล้ว แล้วจึงรันบนบอร์ด ทายก่อนว่ากดปุ่มหนึ่งครั้งหน้าเว็บจะเห็นอะไรเปลี่ยน broker สาธารณะไม่เข้ารหัส ใครก็ subscribe อ่านได้ ห้ามส่งของที่เป็นความลับ และฟังเฉพาะbento-aiot/<ทีม>/#ห้าม subscribe#08_class_race.pyใช้เมื่อเรียนพร้อมกันหลายทีมและผู้สอนฉายclass_game.htmlก่อนรอบแรกให้เขียนคำทายลงบันทึกการเรียนว่า ทีมที่ผลขึ้นจอหน้าห้องก่อนคือทีมที่เร็วที่สุดหรือไม่ เพราะอะไร แล้วลองข้อ 2 ในบล็อก “ตาคุณ” ท้ายไฟล์เพื่อวัดผลของกันเด้ง 40 ms เอง ระหว่างรอบ ลองหาว่าบรรทัดไหนในไฟล์ที่ปฏิเสธคำสั่งledหรือbeepที่ทีมอื่นส่งเข้ามาแกล้ง
| ไฟล์ | ไฟล์นี้สอน |
|---|---|
| examples/07_button_to_broker.py | ปุ่มกับไฟบนโต๊ะเรา ขึ้นไปอยู่บน broker ให้หน้าเว็บอ่านได้ |
| examples/08_class_race.py | เกมกดเร็วทั้งห้อง ผู้สอนกดเริ่มรอบ บอร์ดทุกทีมจับเวลาเอง |
สไลด์ของบทเรียนนี้อ้างถึงไฟล์ที่อยู่ในบทเรียนอื่นด้วย:
- m02-ui-to-hardware/l01-gpio-leds-buttons/examples/01_board_info.py — ถามบอร์ดก่อนว่ามีอะไรให้เล่นบ้าง
- m02-ui-to-hardware/l01-gpio-leds-buttons/examples/03_led_brightness.py — หรี่ไฟค้างไว้ให้นานพอที่ตาจะเทียบสองระดับได้
- m02-ui-to-hardware/l02-active-low-debounce/examples/02_led_blink.py — ทำให้ไฟกะพริบเป็นจังหวะ แล้วนับรอบที่กะพริบไปแล้ว
- m02-ui-to-hardware/l02-active-low-debounce/examples/04_button_active_low.py — ปุ่มนี้ 0 คือกด ไม่ใช่ 1
- m02-ui-to-hardware/l02-active-low-debounce/examples/05_debounce_count.py — นับการกดให้ตรง ด้วยการรอให้ปุ่มนิ่งก่อน
- m02-ui-to-hardware/l02-active-low-debounce/examples/06_button_picks_led.py — ปุ่มเดียวคุมไฟทุกดวง ด้วยการจำสถานะเอง
- shared/usecase/01_andon_severity_lamp.py — เสาไฟสถานะแบบโรงงาน (andon light)
- shared/web/class_game.html — เกมกดเร็วทั้งห้อง
- shared/web/mqtt_dashboard.html — แดชบอร์ดทั้งห้อง
- shared/web/my_first_reader.html — อ่านค่าจากบอร์ด
ภาพจอจาก BENTO Emulator ของตัวอย่างในบทนี้ (คลิกชื่อไฟล์เพื่อเปิดโค้ด)

07_button_to_broker.py ปุ่มกับไฟบนโต๊ะเรา ขึ้นไปอยู่บน broker ให้หน้าเว็บอ่านได้
08_class_race.py เกมกดเร็วทั้งห้อง ผู้สอนกดเริ่มรอบ บอร์ดทุกทีมจับเวลาเองฝึกเติม
หัวข้อที่มีชื่อว่า “ฝึกเติม”ไฟล์ฝึกมีช่องว่าง # เติม: หกจุด ระวังการเยื้องบรรทัด เพราะสามจุดกลางอยู่คนละชั้นกัน ถ้าเยื้องผิด Python จะเอาโค้ดไปไว้ผิดเงื่อนไข
- จุด 1:
gpio.led(i).off()ในลูปดับไฟก่อนเริ่ม - จุด 2:
led_index = (led_index + 1) % NUM_LEDSระหว่างดับดวงเดิมกับจุดดวงใหม่ - จุด 3:
raw = btn.is_pressed()ต้นท่าที่ 4 - จุด 4:
last_change = nowในกิ่งที่ค่าดิบเพิ่งขยับ - จุด 5:
count += 1ใต้if stable: - จุด 6: บรรทัด
lcd.printสรุปสีเขียวหลังลูป
เติมจุด 1–2 แล้วรันก่อน ไฟต้องวิ่งครบทุกดวง (ตอนนี้กดปุ่มแล้วเลขยังไม่ขยับ เพราะ raw ถูกตั้งเป็น False ไว้ให้ไฟล์รันได้)
จากนั้นเติมจุด 3–5 แล้วกดสิบครั้ง ต้องได้สิบพอดี สุดท้ายเติมจุด 6 พอไฟวิ่งได้แล้วปุ่มพัง เรารู้ทันทีว่าปัญหาไม่ได้อยู่ที่ไฟ
ติดตรงไหนให้เปิดไฟล์ในบทเรียน 2.2: ไฟค้างหรือจบแล้วติดค้างดู 02_led_blink.py ปุ่มกลับด้านดู 04_button_active_low.py
เลขเกินจริงดู 05_debounce_count.py และจำไม่ได้ว่าดวงไหนติดดู 06_button_picks_led.py
| ไฟล์ฝึก | เรื่อง |
|---|---|
| practice/s03_led_button.py | ไฟวิ่งทุกดวง + ปุ่มนับครั้งแบบกันเด้ง (ฉบับฝึกเติมโค้ด) |
เปิดเฉลยหลังจากลองเองแล้วอย่างน้อยหนึ่งรอบ แล้วอ่าน วิธีใช้เฉลย ก่อน
| เฉลย | คู่กับ |
|---|---|
| solution/s03_led_button.py | practice/s03_led_button.py |
เช็กความเข้าใจ
หัวข้อที่มีชื่อว่า “เช็กความเข้าใจ”คำถามชุดเดียวกันอยู่ใน quiz.yaml สำหรับระบบที่ตรวจอัตโนมัติ
-
ทีมเติมเฉพาะจุด 1–2 ในไฟล์ฝึกแล้วกด Program to Device จะเห็นอะไร (เลือกหนึ่งข้อ · เป้าหมายข้อ 1)
- ก) ไฟวิ่งครบทุกดวง แต่กดปุ่มแล้วตัวนับไม่ขยับ เพราะ raw ยังเป็น False ที่ตั้งไว้ให้ไฟล์รันได้
- ข) โปรแกรมหยุดทันทีด้วย NameError เพราะยังไม่มีบรรทัด raw = btn.is_pressed()
- ค) ไฟไม่วิ่ง เพราะลูปต้องรอให้กดปุ่มก่อนจึงจะขยับไฟ
- ง) ไฟวิ่งและนับปุ่มได้ แต่นับเกินจริง เพราะยังไม่ได้เติมส่วนกันเด้ง
เฉลย
ก — ไฟวิ่งเป็นงานที่ไม่พึ่งอินพุตจากใคร จึงทำงานได้ทันทีหลังจุด 1–2 ส่วน raw ถูกตั้งเป็น False ไว้ก่อน ไฟล์จึงไม่พัง แต่ปุ่มยังไม่ถูกอ่าน การเติมทีละกลุ่มแบบนี้ทำให้รู้ว่าถ้าปุ่มพังตอนหลัง ปัญหาไม่ได้อยู่ที่ไฟ
-
อาการคู่กับสาเหตุข้อใดถูกต้อง เลือกทุกข้อที่ถูก (เลือกได้หลายข้อ · เป้าหมายข้อ 2)
- ก) กดหนึ่งครั้ง เลขขึ้น 3–5 ← ยังไม่ได้กันเด้ง หรือ DEBOUNCE_MS น้อยเกิน
- ข) กดแล้วเลขไม่ขึ้นเลย ← มี sleep_ms ยาวในลูป ปุ่มหลุดตอนหลับ
- ค) ไฟติดพร้อมกันสองดวง ← จุดดวงใหม่ก่อนดับดวงเดิม
- ง) จอไม่ขึ้นตัวเลขแต่ไฟวิ่งปกติ ← DEBOUNCE_MS สูงเกินไป
- จ) ไฟไม่วิ่ง ค้างดวงเดียว ← ลืม if stable
เฉลย
ก, ข, ค — สามข้อแรกตรงกับตารางกับดัก ส่วนจอไม่ขึ้นตัวเลขทั้งที่ไฟวิ่งเกิดจากไม่ได้เปิดหน้า Playground ไว้ และไฟค้างดวงเดียวเกิดจาก last_step = now อยู่นอก if ส่วนการลืม if stable ทำให้เลขขึ้นทีละสอง
-
ทำไม 07_button_to_broker.py ส่ง telemetry ซ้ำทุก 2 วินาที ทั้งที่สถานะอาจไม่ได้เปลี่ยนเลย (เลือกหนึ่งข้อ · เป้าหมายข้อ 3)
- ก) เพราะ publish() ของเฟิร์มแวร์นี้ส่ง retain = false เสมอ broker ไม่เก็บใบล่าสุดไว้ หน้าเว็บที่เปิดทีหลังจึงต้องรอใบถัดไป
- ข) เพราะ broker สาธารณะตัดการเชื่อมต่อถ้าไม่มีข้อมูลเข้าภายใน 2 วินาที
- ค) เพราะกล่องรับของบอร์ดมีช่องเดียว ต้องส่งออกก่อนจึงจะรับได้
- ง) เพราะ event อาจส่งซ้ำ จึงต้องมี telemetry ไว้ลบใบที่ซ้ำ
เฉลย
ก — publish() ส่ง retain = false เสมอแม้ docstring จะเขียนว่ารับ retain= ถ้าส่งเฉพาะตอนเปลี่ยน หน้าเว็บที่เปิดทีหลังจะจอว่าง การส่งซ้ำทุก 2 วินาทีทำให้หน้าเว็บที่เปิดช้าที่สุดก็ตามทันภายใน 2 วินาที
-
ข้อใดถูกต้องเกี่ยวกับการเขียน 07_button_to_broker.py รอบข้อจำกัดของเฟิร์มแวร์ เลือกทุกข้อที่ถูก (เลือกได้หลายข้อ · เป้าหมายข้อ 3)
- ก) ต้องกันเด้งก่อน publish ไม่งั้นกดหนึ่งครั้งกลายเป็นหลายใบบน broker และคนที่อ่านอีกฝั่งแยกไม่ออกว่าใบไหนคือการกดจริง
- ข) ส่งสถานะไฟจากตัวแปร leds ที่ handle() จดไว้ ไม่ใช่จาก gpio.led(i).value()
- ค) หยิบ get_message() ทุกรอบลูป เพราะกล่องรับมีช่องเดียว ใบใหม่ทับใบเก่า
- ง) สองคนในทีมรันไฟล์ 07 ด้วย TEAM เดียวกันบนสองบอร์ดพร้อมกันได้ เพราะ broker แยกให้เอง
- จ) ถ้าสายหลุด publish() คืน False ให้เองอยู่แล้ว ไม่ต้องดักอะไร
เฉลย
ก, ข, ค — client_id ซ้ำกันไม่ได้ ใครต่อทีหลังจะเตะคนก่อนหลุด ไฟล์ 07 ใช้ bento-aiot- ต่อด้วยชื่อทีม จึงรันได้บอร์ดเดียวต่อทีม และ publish() ตอนสายหลุดโยน OSError ไม่ได้คืน False จึงต้องให้ send() ดักไว้
-
ข้อใดถูกต้องเกี่ยวกับการจับเวลาในเกมกดเร็วทั้งห้อง (08_class_race.py) เลือกทุกข้อที่ถูก (เลือกได้หลายข้อ · เป้าหมายข้อ 4)
- ก) ทีมที่ผลขึ้นจอหน้าห้องก่อนอาจไม่ใช่ทีมที่เร็วที่สุด เพราะแต่ละบอร์ดสุ่มเวลารอเอง และข้อความเดินทางใช้เวลาไม่เท่ากัน
- ข) บอร์ดจดเวลาตอนขาเริ่มเปลี่ยน แล้วค่อยใช้เวลานั้นเมื่อกันเด้งยืนยันว่าเป็นการกดจริง
- ค) ถ้าจับเวลาตอนที่กันเด้งยืนยัน ทุกทีมจะได้เวลาเร็วกว่าจริง 40 ms
- ง) กดเร็วกว่า 100 ms หลังไฟติดถือเป็นสถิติที่ดีที่สุดของรอบ
เฉลย
ก, ข — เราวัดบนบอร์ดระหว่างหลอดของมันเองกับปุ่มของมันเอง ทางเดินของข้อความจึงไม่อยู่ในตัวเลข กันเด้ง 40 ms ทำให้บอร์ดเชื่อช้ากว่านิ้ว ถ้าจับเวลาตอนเชื่อทุกทีมจะช้าเกินจริง และเร็วกว่า 100 ms นับเป็นออกตัวก่อน
MVP ของชุดบทเรียน 2.1–2.3 ทำบนบอร์ดจริงหรือ Emulator แล้วจดผลลงบันทึกการเรียน
- ไฟวิ่งไล่กันเป็นวงครบทุกดวงที่
gpio.num_leds()บอก ไม่มีดวงไหนค้างติดหรือข้ามดวง (Dev Kit: ถ้ามองไม่เห็น LED1/LED2 บนโมดูล ให้ดูดวง RGB สามดวงเป็นหลัก แล้วจดว่าเห็นกี่ดวง) - แก้
STEP_MSแล้วรันใหม่ จังหวะไฟเปลี่ยนตามจริง อย่างน้อยสองค่า - กดปุ่มผู้ใช้ (
gpio.button(0)) สิบครั้ง ตัวเลขบนจอขึ้นสิบพอดี บน Dev Kit ห้ามโยกสวิตช์ใดบนฐาน เพราะหลายตัวคือสวิตช์ตัดไฟ - อธิบายให้ผู้สอนหรือเพื่อนร่วมทีมฟังได้ว่าถ้าเอาโค้ดกันเด้งออกจะเกิดอะไรและเพราะอะไร
- จบโปรแกรมแล้วไฟทุกดวงดับ พร้อมบรรทัดสรุปสีเขียวบนจอ
- ถ่ายรูปหรือคลิปสั้นแนบในบันทึกการเรียน
- (Eva Kit) รันซ้ำโดยแตะการ์ด Controls ค้างไว้แทน Playground แล้วดูวงกลมบนจอติดดับตามไฟวิ่ง ทั้งที่โค้ดไม่ได้สั่งจอสักบรรทัด
ขึ้น broker (ต่อจาก MVP)
- หน้า
my_first_reader.htmlขึ้น “ต่อแล้ว (bento-aiot/<ทีม>)” และภายใน 2 วินาทีหลังรัน 07 เห็นกล่องpressesbtnleds - กดปุ่มบนบอร์ด ช่อง “เหตุการณ์ล่าสุด” เปลี่ยนทันที
- กด “เปิดไฟ LED 0” บนหน้าเว็บ แล้ว
ledsในใบถัดไปเปลี่ยนตาม (Dev Kit ดวง 0 อยู่บนโมดูล อาจมองไม่เห็นหลอด แต่ledsยังเปลี่ยนให้เห็น) - ต่อไม่ติด ให้เปลี่ยนทั้งสองฝั่งไปตัวสำรอง
test.mosquitto.org(บอร์ด 1883 · เว็บwss://test.mosquitto.org:8081/mqtt) แล้วจดว่าตัวไหนใช้ได้
การบ้านของทีม: เลือกหนึ่งข้อต่อยอดในสไลด์ (ปุ่มวนเปลี่ยนความเร็ว 400/200/100 ms · รูปแบบไฟไป-กลับที่สร้างจาก gpio.num_leds()
· แยกกดสั้นกับกดยาวที่ 800 ms · ทดลอง DEBOUNCE_MS 0, 5, 40, 200 แล้วทำตาราง) หรือทำงานประยุกต์ที่ส่งค่าที่ทีมเลือกขึ้น telemetry
แล้วแก้ my_first_reader.html ให้ตอบสนองค่านั้น บทเรียน 2.4 เปลี่ยนจากปุ่มจริงหนึ่งปุ่มเป็นปุ่มบนจอสัมผัสที่เราสร้างเอง
บทเรียนถัดไป: บทเรียน 2.4 — จอสัมผัสและ widget ตัวแรก
สะท้อนคิด
หัวข้อที่มีชื่อว่า “สะท้อนคิด”- ตอนเติมช่องว่าง ครั้งไหนที่เติมทีละกลุ่มแล้วช่วยให้หาจุดพังได้เร็วขึ้น ถ้าเติมครบหกจุดแล้วรันทีเดียว คุณจะเริ่มหาจากตรงไหน
- ถ้าเปลี่ยนจาก “อ่านปุ่ม” เป็น “อ่านความเร่งจาก IMU” ลูปนี้ต้องแก้กี่บรรทัด และการกันเด้งเหมือนการกรองสัญญาณเซนเซอร์ตรงไหน
- ใครก็ส่งเข้าหัวข้อของทีมไหนก็ได้บน broker สาธารณะ ข้อมูลอะไรของทีมคุณที่ไม่ควรขึ้นไปอยู่ตรงนั้น
คำถามทบทวน
ลองตอบเองก่อน แล้วค่อยเปิดดูเฉลย
-
ทีมเติมเฉพาะจุด 1–2 ในไฟล์ฝึกแล้วกด Program to Device จะเห็นอะไร (เป้าหมายข้อ 1)
- ไฟวิ่งครบทุกดวง แต่กดปุ่มแล้วตัวนับไม่ขยับ เพราะ raw ยังเป็น False ที่ตั้งไว้ให้ไฟล์รันได้
- โปรแกรมหยุดทันทีด้วย NameError เพราะยังไม่มีบรรทัด raw = btn.is_pressed()
- ไฟไม่วิ่ง เพราะลูปต้องรอให้กดปุ่มก่อนจึงจะขยับไฟ
- ไฟวิ่งและนับปุ่มได้ แต่นับเกินจริง เพราะยังไม่ได้เติมส่วนกันเด้ง
ดูเฉลย
คำตอบ: A. ไฟวิ่งครบทุกดวง แต่กดปุ่มแล้วตัวนับไม่ขยับ เพราะ raw ยังเป็น False ที่ตั้งไว้ให้ไฟล์รันได้
ไฟวิ่งเป็นงานที่ไม่พึ่งอินพุตจากใคร จึงทำงานได้ทันทีหลังจุด 1–2 ส่วน raw ถูกตั้งเป็น False ไว้ก่อน ไฟล์จึงไม่พัง แต่ปุ่มยังไม่ถูกอ่าน การเติมทีละกลุ่มแบบนี้ทำให้รู้ว่าถ้าปุ่มพังตอนหลัง ปัญหาไม่ได้อยู่ที่ไฟ
-
อาการคู่กับสาเหตุข้อใดถูกต้อง เลือกทุกข้อที่ถูก (เป้าหมายข้อ 2)
- กดหนึ่งครั้ง เลขขึ้น 3–5 ← ยังไม่ได้กันเด้ง หรือ DEBOUNCE_MS น้อยเกิน
- กดแล้วเลขไม่ขึ้นเลย ← มี sleep_ms ยาวในลูป ปุ่มหลุดตอนหลับ
- ไฟติดพร้อมกันสองดวง ← จุดดวงใหม่ก่อนดับดวงเดิม
- จอไม่ขึ้นตัวเลขแต่ไฟวิ่งปกติ ← DEBOUNCE_MS สูงเกินไป
- ไฟไม่วิ่ง ค้างดวงเดียว ← ลืม if stable
ดูเฉลย
คำตอบ: A. กดหนึ่งครั้ง เลขขึ้น 3–5 ← ยังไม่ได้กันเด้ง หรือ DEBOUNCE_MS น้อยเกิน · B. กดแล้วเลขไม่ขึ้นเลย ← มี sleep_ms ยาวในลูป ปุ่มหลุดตอนหลับ · C. ไฟติดพร้อมกันสองดวง ← จุดดวงใหม่ก่อนดับดวงเดิม
สามข้อแรกตรงกับตารางกับดัก ส่วนจอไม่ขึ้นตัวเลขทั้งที่ไฟวิ่งเกิดจากไม่ได้เปิดหน้า Playground ไว้ และไฟค้างดวงเดียวเกิดจาก last_step = now อยู่นอก if ส่วนการลืม if stable ทำให้เลขขึ้นทีละสอง
-
ทำไม 07_button_to_broker.py ส่ง telemetry ซ้ำทุก 2 วินาที ทั้งที่สถานะอาจไม่ได้เปลี่ยนเลย (เป้าหมายข้อ 3)
- เพราะ publish() ของเฟิร์มแวร์นี้ส่ง retain = false เสมอ broker ไม่เก็บใบล่าสุดไว้ หน้าเว็บที่เปิดทีหลังจึงต้องรอใบถัดไป
- เพราะ broker สาธารณะตัดการเชื่อมต่อถ้าไม่มีข้อมูลเข้าภายใน 2 วินาที
- เพราะกล่องรับของบอร์ดมีช่องเดียว ต้องส่งออกก่อนจึงจะรับได้
- เพราะ event อาจส่งซ้ำ จึงต้องมี telemetry ไว้ลบใบที่ซ้ำ
ดูเฉลย
คำตอบ: A. เพราะ publish() ของเฟิร์มแวร์นี้ส่ง retain = false เสมอ broker ไม่เก็บใบล่าสุดไว้ หน้าเว็บที่เปิดทีหลังจึงต้องรอใบถัดไป
publish() ส่ง retain = false เสมอแม้ docstring จะเขียนว่ารับ retain= ถ้าส่งเฉพาะตอนเปลี่ยน หน้าเว็บที่เปิดทีหลังจะจอว่าง การส่งซ้ำทุก 2 วินาทีทำให้หน้าเว็บที่เปิดช้าที่สุดก็ตามทันภายใน 2 วินาที
-
ข้อใดถูกต้องเกี่ยวกับการเขียน 07_button_to_broker.py รอบข้อจำกัดของเฟิร์มแวร์ เลือกทุกข้อที่ถูก (เป้าหมายข้อ 3)
- ต้องกันเด้งก่อน publish ไม่งั้นกดหนึ่งครั้งกลายเป็นหลายใบบน broker และคนที่อ่านอีกฝั่งแยกไม่ออกว่าใบไหนคือการกดจริง
- ส่งสถานะไฟจากตัวแปร leds ที่ handle() จดไว้ ไม่ใช่จาก gpio.led(i).value()
- หยิบ get_message() ทุกรอบลูป เพราะกล่องรับมีช่องเดียว ใบใหม่ทับใบเก่า
- สองคนในทีมรันไฟล์ 07 ด้วย TEAM เดียวกันบนสองบอร์ดพร้อมกันได้ เพราะ broker แยกให้เอง
- ถ้าสายหลุด publish() คืน False ให้เองอยู่แล้ว ไม่ต้องดักอะไร
ดูเฉลย
คำตอบ: A. ต้องกันเด้งก่อน publish ไม่งั้นกดหนึ่งครั้งกลายเป็นหลายใบบน broker และคนที่อ่านอีกฝั่งแยกไม่ออกว่าใบไหนคือการกดจริง · B. ส่งสถานะไฟจากตัวแปร leds ที่ handle() จดไว้ ไม่ใช่จาก gpio.led(i).value() · C. หยิบ get_message() ทุกรอบลูป เพราะกล่องรับมีช่องเดียว ใบใหม่ทับใบเก่า
client_id ซ้ำกันไม่ได้ ใครต่อทีหลังจะเตะคนก่อนหลุด ไฟล์ 07 ใช้ bento-aiot- ต่อด้วยชื่อทีม จึงรันได้บอร์ดเดียวต่อทีม และ publish() ตอนสายหลุดโยน OSError ไม่ได้คืน False จึงต้องให้ send() ดักไว้
-
ข้อใดถูกต้องเกี่ยวกับการจับเวลาในเกมกดเร็วทั้งห้อง (08_class_race.py) เลือกทุกข้อที่ถูก (เป้าหมายข้อ 4)
- ทีมที่ผลขึ้นจอหน้าห้องก่อนอาจไม่ใช่ทีมที่เร็วที่สุด เพราะแต่ละบอร์ดสุ่มเวลารอเอง และข้อความเดินทางใช้เวลาไม่เท่ากัน
- บอร์ดจดเวลาตอนขาเริ่มเปลี่ยน แล้วค่อยใช้เวลานั้นเมื่อกันเด้งยืนยันว่าเป็นการกดจริง
- ถ้าจับเวลาตอนที่กันเด้งยืนยัน ทุกทีมจะได้เวลาเร็วกว่าจริง 40 ms
- กดเร็วกว่า 100 ms หลังไฟติดถือเป็นสถิติที่ดีที่สุดของรอบ
ดูเฉลย
คำตอบ: A. ทีมที่ผลขึ้นจอหน้าห้องก่อนอาจไม่ใช่ทีมที่เร็วที่สุด เพราะแต่ละบอร์ดสุ่มเวลารอเอง และข้อความเดินทางใช้เวลาไม่เท่ากัน · B. บอร์ดจดเวลาตอนขาเริ่มเปลี่ยน แล้วค่อยใช้เวลานั้นเมื่อกันเด้งยืนยันว่าเป็นการกดจริง
เราวัดบนบอร์ดระหว่างหลอดของมันเองกับปุ่มของมันเอง ทางเดินของข้อความจึงไม่อยู่ในตัวเลข กันเด้ง 40 ms ทำให้บอร์ดเชื่อช้ากว่านิ้ว ถ้าจับเวลาตอนเชื่อทุกทีมจะช้าเกินจริง และเร็วกว่า 100 ms นับเป็นออกตัวก่อน
อ้างอิงบทเรียนนี้
ถ้านำบทเรียนนี้ไปสอน ทำสไลด์ หรือทำเอกสารต่อ ให้อ้างอิงด้วยข้อความนี้ ถ้าดัดแปลงเนื้อหา ให้เติม (ดัดแปลง)ต่อท้ายชื่อบทเรียน
"ลงมือทำ: ไฟวิ่งกับปุ่ม แล้วส่งขึ้น broker" จาก TESA Open Knowledge โดยสมาคมสมองกลฝังตัวไทย (Thai Embedded Systems Association: TESA) https://github.com/tesaiot/tesa-qualification-program สัญญาอนุญาต CC BY-NC 4.0
ข้อความอ้างอิงภาษาอังกฤษ: "Hands-on: running lights, a button, and the broker" from TESA Open Knowledge by the Thai Embedded Systems Association (TESA), https://github.com/tesaiot/tesa-qualification-program, licensed under CC BY-NC 4.0
ลิงก์บทเรียน: https://tesaiot.github.io/tesa-qualification-program/courses/aiot-micropython/m02-ui-to-hardware/l03-led-button-lab/
บทเรียนนี้ดัดแปลงจากต้นฉบับด้านล่าง เมื่ออ้างอิงให้คงเครดิตต้นฉบับไว้ด้วย
https://github.com/Advance-Innovation-Centre-AIC/embedded-systems-for-aiot-developer/blob/a80bbe88a34bcb9bb8d991f42f9252b77cdab079/session-03.html (slides 28–52)
TESA Open Knowledge · © 2026 สมาคมสมองกลฝังตัวไทย (TESA) · CC BY-NC 4.0
เนื้อหาเผยแพร่ภายใต้ CC BY-NC 4.0 นำไปใช้ต่อในงานที่ไม่ใช่เพื่อการค้าได้ โปรดอ้างอิงสมาคมสมองกลฝังตัวไทย (TESA) ทุกครั้ง · วิธีอ้างอิง TESA