ข้ามไปยังเนื้อหา

ลงมือทำ: ไฟวิ่งกับปุ่ม แล้วส่งขึ้น broker

โมดูล 2 — จากจอสู่ฮาร์ดแวร์ · สไลด์: slides.md · ภาพรวมโมดูล · หน้าหลักสูตร

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

เมื่อจบบทเรียนนี้ คุณจะ:

  1. เติมช่องว่างหกจุดใน practice/s03_led_button.py จนผ่าน MVP คือไฟวิ่งครบทุกดวงที่ gpio.num_leds() บอก แก้ STEP_MS อย่างน้อยสองค่าแล้วจังหวะเปลี่ยนตาม กดปุ่มผู้ใช้สิบครั้งตัวเลขขึ้นสิบพอดี และจบโปรแกรมด้วยไฟดับทุกดวงพร้อมบรรทัดสรุปสีเขียว
  2. จับคู่อาการที่เจอบ่อยกับสาเหตุและวิธีแก้ได้ (เลขขึ้นทีละ 3–5 ขึ้นทีละ 2 ไม่ขึ้นเลย ไฟค้างดวงเดียว ไฟติดสองดวง) และอธิบายให้ผู้สอนฟังได้ว่าถ้าเอาโค้ดกันเด้งออกจะเกิดอะไรและเพราะอะไร
  3. รัน 07_button_to_broker.py ด้วยชื่อทีมของตัวเองจนหน้า my_first_reader.html เห็น telemetry ทุก 2 วินาทีและ event ทันทีที่กด แล้วอธิบายได้ว่า retain ที่เป็น false เสมอ กล่องรับช่องเดียว OSError ตอนสายหลุด และ client_id ที่ซ้ำไม่ได้ ทำให้โค้ดต้องหน้าตาแบบนี้อย่างไร
  4. อธิบายได้ว่าทำไมเกมกดเร็วทั้งห้องจับเวลาบนบอร์ดแทนลำดับที่ผลมาถึง และทำไมจดเวลาตอนขาเริ่มเปลี่ยนแทนตอนที่กันเด้งยืนยัน

ต้องผ่านบทเรียน 2.2 มาแล้ว เพราะบทเรียนนี้ประกอบห้าท่าของไฟล์เฉลยที่แกะในบทเรียนนั้นให้เป็นโปรแกรมของทีม ส่วน broker ใช้ของชุดเดิมจากบทเรียน 1.4–1.6 คือ Hotspot มือถือของทีม (ชื่อกับรหัส) และรหัส TEAM ที่ไม่ซ้ำใครชุดเดิม เช่น nok4821 (ถ้าเรียนเป็นกลุ่ม ใช้เลขทีมที่ผู้จัดแจก) บนจอบอร์ดแตะการ์ด BENTO Playground ค้างไว้ และเตรียมกล้องมือถือไว้ถ่ายไฟกับจอเก็บลงบันทึกการเรียน

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 จึงรันได้บอร์ดเดียวต่อทีม

  1. 07_button_to_broker.py แก้สี่บรรทัดบนสุด (WIFI_SSID WIFI_PASS BROKER TEAM) ห้ามแก้ ROOT กับ DEVICE_ID ถ้าไม่แก้ TEAM จาก teamXX โปรแกรมไม่ยอมรัน เปิดหน้า my_first_reader.html ด้วยลิงก์ในสไลด์ที่ต่อท้าย ?team= ด้วยรหัสเดียวกับ TEAM รอให้บรรทัดสถานะขึ้นว่าต่อแล้ว แล้วจึงรันบนบอร์ด ทายก่อนว่ากดปุ่มหนึ่งครั้งหน้าเว็บจะเห็นอะไรเปลี่ยน broker สาธารณะไม่เข้ารหัส ใครก็ subscribe อ่านได้ ห้ามส่งของที่เป็นความลับ และฟังเฉพาะ bento-aiot/<ทีม>/# ห้าม subscribe #
  2. 08_class_race.py ใช้เมื่อเรียนพร้อมกันหลายทีมและผู้สอนฉาย class_game.html ก่อนรอบแรกให้เขียนคำทายลงบันทึกการเรียนว่า ทีมที่ผลขึ้นจอหน้าห้องก่อนคือทีมที่เร็วที่สุดหรือไม่ เพราะอะไร แล้วลองข้อ 2 ในบล็อก “ตาคุณ” ท้ายไฟล์เพื่อวัดผลของกันเด้ง 40 ms เอง ระหว่างรอบ ลองหาว่าบรรทัดไหนในไฟล์ที่ปฏิเสธคำสั่ง led หรือ beep ที่ทีมอื่นส่งเข้ามาแกล้ง
ไฟล์ ไฟล์นี้สอน
examples/07_button_to_broker.py ปุ่มกับไฟบนโต๊ะเรา ขึ้นไปอยู่บน broker ให้หน้าเว็บอ่านได้
examples/08_class_race.py เกมกดเร็วทั้งห้อง ผู้สอนกดเริ่มรอบ บอร์ดทุกทีมจับเวลาเอง

สไลด์ของบทเรียนนี้อ้างถึงไฟล์ที่อยู่ในบทเรียนอื่นด้วย:

ภาพจอจาก BENTO Emulator ของตัวอย่างในบทนี้ (คลิกชื่อไฟล์เพื่อเปิดโค้ด)

จอของ examples/07_button_to_broker.py ขณะรันใน BENTO Emulator: ปุ่มกับไฟบนโต๊ะเรา ขึ้นไปอยู่บน broker ให้หน้าเว็บอ่านได้
07_button_to_broker.py ปุ่มกับไฟบนโต๊ะเรา ขึ้นไปอยู่บน broker ให้หน้าเว็บอ่านได้
จอของ examples/08_class_race.py ขณะรันใน BENTO Emulator: เกมกดเร็วทั้งห้อง ผู้สอนกดเริ่มรอบ บอร์ดทุกทีมจับเวลาเอง
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. ทีมเติมเฉพาะจุด 1–2 ในไฟล์ฝึกแล้วกด Program to Device จะเห็นอะไร (เลือกหนึ่งข้อ · เป้าหมายข้อ 1)

    • ก) ไฟวิ่งครบทุกดวง แต่กดปุ่มแล้วตัวนับไม่ขยับ เพราะ raw ยังเป็น False ที่ตั้งไว้ให้ไฟล์รันได้
    • ข) โปรแกรมหยุดทันทีด้วย NameError เพราะยังไม่มีบรรทัด raw = btn.is_pressed()
    • ค) ไฟไม่วิ่ง เพราะลูปต้องรอให้กดปุ่มก่อนจึงจะขยับไฟ
    • ง) ไฟวิ่งและนับปุ่มได้ แต่นับเกินจริง เพราะยังไม่ได้เติมส่วนกันเด้ง
    เฉลย

    ก — ไฟวิ่งเป็นงานที่ไม่พึ่งอินพุตจากใคร จึงทำงานได้ทันทีหลังจุด 1–2 ส่วน raw ถูกตั้งเป็น False ไว้ก่อน ไฟล์จึงไม่พัง แต่ปุ่มยังไม่ถูกอ่าน การเติมทีละกลุ่มแบบนี้ทำให้รู้ว่าถ้าปุ่มพังตอนหลัง ปัญหาไม่ได้อยู่ที่ไฟ

  2. อาการคู่กับสาเหตุข้อใดถูกต้อง เลือกทุกข้อที่ถูก (เลือกได้หลายข้อ · เป้าหมายข้อ 2)

    • ก) กดหนึ่งครั้ง เลขขึ้น 3–5 ← ยังไม่ได้กันเด้ง หรือ DEBOUNCE_MS น้อยเกิน
    • ข) กดแล้วเลขไม่ขึ้นเลย ← มี sleep_ms ยาวในลูป ปุ่มหลุดตอนหลับ
    • ค) ไฟติดพร้อมกันสองดวง ← จุดดวงใหม่ก่อนดับดวงเดิม
    • ง) จอไม่ขึ้นตัวเลขแต่ไฟวิ่งปกติ ← DEBOUNCE_MS สูงเกินไป
    • จ) ไฟไม่วิ่ง ค้างดวงเดียว ← ลืม if stable
    เฉลย

    ก, ข, ค — สามข้อแรกตรงกับตารางกับดัก ส่วนจอไม่ขึ้นตัวเลขทั้งที่ไฟวิ่งเกิดจากไม่ได้เปิดหน้า Playground ไว้ และไฟค้างดวงเดียวเกิดจาก last_step = now อยู่นอก if ส่วนการลืม if stable ทำให้เลขขึ้นทีละสอง

  3. ทำไม 07_button_to_broker.py ส่ง telemetry ซ้ำทุก 2 วินาที ทั้งที่สถานะอาจไม่ได้เปลี่ยนเลย (เลือกหนึ่งข้อ · เป้าหมายข้อ 3)

    • ก) เพราะ publish() ของเฟิร์มแวร์นี้ส่ง retain = false เสมอ broker ไม่เก็บใบล่าสุดไว้ หน้าเว็บที่เปิดทีหลังจึงต้องรอใบถัดไป
    • ข) เพราะ broker สาธารณะตัดการเชื่อมต่อถ้าไม่มีข้อมูลเข้าภายใน 2 วินาที
    • ค) เพราะกล่องรับของบอร์ดมีช่องเดียว ต้องส่งออกก่อนจึงจะรับได้
    • ง) เพราะ event อาจส่งซ้ำ จึงต้องมี telemetry ไว้ลบใบที่ซ้ำ
    เฉลย

    ก — publish() ส่ง retain = false เสมอแม้ docstring จะเขียนว่ารับ retain= ถ้าส่งเฉพาะตอนเปลี่ยน หน้าเว็บที่เปิดทีหลังจะจอว่าง การส่งซ้ำทุก 2 วินาทีทำให้หน้าเว็บที่เปิดช้าที่สุดก็ตามทันภายใน 2 วินาที

  4. ข้อใดถูกต้องเกี่ยวกับการเขียน 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() ดักไว้

  5. ข้อใดถูกต้องเกี่ยวกับการจับเวลาในเกมกดเร็วทั้งห้อง (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 เห็นกล่อง presses btn leds
  • กดปุ่มบนบอร์ด ช่อง “เหตุการณ์ล่าสุด” เปลี่ยนทันที
  • กด “เปิดไฟ 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. ทีมเติมเฉพาะจุด 1–2 ในไฟล์ฝึกแล้วกด Program to Device จะเห็นอะไร (เป้าหมายข้อ 1)

    1. ไฟวิ่งครบทุกดวง แต่กดปุ่มแล้วตัวนับไม่ขยับ เพราะ raw ยังเป็น False ที่ตั้งไว้ให้ไฟล์รันได้
    2. โปรแกรมหยุดทันทีด้วย NameError เพราะยังไม่มีบรรทัด raw = btn.is_pressed()
    3. ไฟไม่วิ่ง เพราะลูปต้องรอให้กดปุ่มก่อนจึงจะขยับไฟ
    4. ไฟวิ่งและนับปุ่มได้ แต่นับเกินจริง เพราะยังไม่ได้เติมส่วนกันเด้ง
    ดูเฉลย

    คำตอบ: A. ไฟวิ่งครบทุกดวง แต่กดปุ่มแล้วตัวนับไม่ขยับ เพราะ raw ยังเป็น False ที่ตั้งไว้ให้ไฟล์รันได้

    ไฟวิ่งเป็นงานที่ไม่พึ่งอินพุตจากใคร จึงทำงานได้ทันทีหลังจุด 1–2 ส่วน raw ถูกตั้งเป็น False ไว้ก่อน ไฟล์จึงไม่พัง แต่ปุ่มยังไม่ถูกอ่าน การเติมทีละกลุ่มแบบนี้ทำให้รู้ว่าถ้าปุ่มพังตอนหลัง ปัญหาไม่ได้อยู่ที่ไฟ

  2. อาการคู่กับสาเหตุข้อใดถูกต้อง เลือกทุกข้อที่ถูก (เป้าหมายข้อ 2)

    1. กดหนึ่งครั้ง เลขขึ้น 3–5 ← ยังไม่ได้กันเด้ง หรือ DEBOUNCE_MS น้อยเกิน
    2. กดแล้วเลขไม่ขึ้นเลย ← มี sleep_ms ยาวในลูป ปุ่มหลุดตอนหลับ
    3. ไฟติดพร้อมกันสองดวง ← จุดดวงใหม่ก่อนดับดวงเดิม
    4. จอไม่ขึ้นตัวเลขแต่ไฟวิ่งปกติ ← DEBOUNCE_MS สูงเกินไป
    5. ไฟไม่วิ่ง ค้างดวงเดียว ← ลืม if stable
    ดูเฉลย

    คำตอบ: A. กดหนึ่งครั้ง เลขขึ้น 3–5 ← ยังไม่ได้กันเด้ง หรือ DEBOUNCE_MS น้อยเกิน · B. กดแล้วเลขไม่ขึ้นเลย ← มี sleep_ms ยาวในลูป ปุ่มหลุดตอนหลับ · C. ไฟติดพร้อมกันสองดวง ← จุดดวงใหม่ก่อนดับดวงเดิม

    สามข้อแรกตรงกับตารางกับดัก ส่วนจอไม่ขึ้นตัวเลขทั้งที่ไฟวิ่งเกิดจากไม่ได้เปิดหน้า Playground ไว้ และไฟค้างดวงเดียวเกิดจาก last_step = now อยู่นอก if ส่วนการลืม if stable ทำให้เลขขึ้นทีละสอง

  3. ทำไม 07_button_to_broker.py ส่ง telemetry ซ้ำทุก 2 วินาที ทั้งที่สถานะอาจไม่ได้เปลี่ยนเลย (เป้าหมายข้อ 3)

    1. เพราะ publish() ของเฟิร์มแวร์นี้ส่ง retain = false เสมอ broker ไม่เก็บใบล่าสุดไว้ หน้าเว็บที่เปิดทีหลังจึงต้องรอใบถัดไป
    2. เพราะ broker สาธารณะตัดการเชื่อมต่อถ้าไม่มีข้อมูลเข้าภายใน 2 วินาที
    3. เพราะกล่องรับของบอร์ดมีช่องเดียว ต้องส่งออกก่อนจึงจะรับได้
    4. เพราะ event อาจส่งซ้ำ จึงต้องมี telemetry ไว้ลบใบที่ซ้ำ
    ดูเฉลย

    คำตอบ: A. เพราะ publish() ของเฟิร์มแวร์นี้ส่ง retain = false เสมอ broker ไม่เก็บใบล่าสุดไว้ หน้าเว็บที่เปิดทีหลังจึงต้องรอใบถัดไป

    publish() ส่ง retain = false เสมอแม้ docstring จะเขียนว่ารับ retain= ถ้าส่งเฉพาะตอนเปลี่ยน หน้าเว็บที่เปิดทีหลังจะจอว่าง การส่งซ้ำทุก 2 วินาทีทำให้หน้าเว็บที่เปิดช้าที่สุดก็ตามทันภายใน 2 วินาที

  4. ข้อใดถูกต้องเกี่ยวกับการเขียน 07_button_to_broker.py รอบข้อจำกัดของเฟิร์มแวร์ เลือกทุกข้อที่ถูก (เป้าหมายข้อ 3)

    1. ต้องกันเด้งก่อน publish ไม่งั้นกดหนึ่งครั้งกลายเป็นหลายใบบน broker และคนที่อ่านอีกฝั่งแยกไม่ออกว่าใบไหนคือการกดจริง
    2. ส่งสถานะไฟจากตัวแปร leds ที่ handle() จดไว้ ไม่ใช่จาก gpio.led(i).value()
    3. หยิบ get_message() ทุกรอบลูป เพราะกล่องรับมีช่องเดียว ใบใหม่ทับใบเก่า
    4. สองคนในทีมรันไฟล์ 07 ด้วย TEAM เดียวกันบนสองบอร์ดพร้อมกันได้ เพราะ broker แยกให้เอง
    5. ถ้าสายหลุด publish() คืน False ให้เองอยู่แล้ว ไม่ต้องดักอะไร
    ดูเฉลย

    คำตอบ: A. ต้องกันเด้งก่อน publish ไม่งั้นกดหนึ่งครั้งกลายเป็นหลายใบบน broker และคนที่อ่านอีกฝั่งแยกไม่ออกว่าใบไหนคือการกดจริง · B. ส่งสถานะไฟจากตัวแปร leds ที่ handle() จดไว้ ไม่ใช่จาก gpio.led(i).value() · C. หยิบ get_message() ทุกรอบลูป เพราะกล่องรับมีช่องเดียว ใบใหม่ทับใบเก่า

    client_id ซ้ำกันไม่ได้ ใครต่อทีหลังจะเตะคนก่อนหลุด ไฟล์ 07 ใช้ bento-aiot- ต่อด้วยชื่อทีม จึงรันได้บอร์ดเดียวต่อทีม และ publish() ตอนสายหลุดโยน OSError ไม่ได้คืน False จึงต้องให้ send() ดักไว้

  5. ข้อใดถูกต้องเกี่ยวกับการจับเวลาในเกมกดเร็วทั้งห้อง (08_class_race.py) เลือกทุกข้อที่ถูก (เป้าหมายข้อ 4)

    1. ทีมที่ผลขึ้นจอหน้าห้องก่อนอาจไม่ใช่ทีมที่เร็วที่สุด เพราะแต่ละบอร์ดสุ่มเวลารอเอง และข้อความเดินทางใช้เวลาไม่เท่ากัน
    2. บอร์ดจดเวลาตอนขาเริ่มเปลี่ยน แล้วค่อยใช้เวลานั้นเมื่อกันเด้งยืนยันว่าเป็นการกดจริง
    3. ถ้าจับเวลาตอนที่กันเด้งยืนยัน ทุกทีมจะได้เวลาเร็วกว่าจริง 40 ms
    4. กดเร็วกว่า 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 ฉบับเต็ม

TESA Open Knowledge · © 2026 สมาคมสมองกลฝังตัวไทย (TESA) · CC BY-NC 4.0

เนื้อหาเผยแพร่ภายใต้ CC BY-NC 4.0 นำไปใช้ต่อในงานที่ไม่ใช่เพื่อการค้าได้ โปรดอ้างอิงสมาคมสมองกลฝังตัวไทย (TESA) ทุกครั้ง · วิธีอ้างอิง TESA