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

ลงมือทำ: แผงควบคุมสัมผัสของเรา และ widget ขั้นต่อไป

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

สร้างแผงควบคุม LED สามสีบนจอสัมผัสที่ไฟสถานะบนจอตรงกับหลอดจริงเสมอ ด้วยปุ่มเปิดกับปุ่มปิดที่แยกกัน ฟังก์ชัน set_led() ที่เป็นประตูเดียว และกล่องยืนยันก่อนปิดทั้งหมด แล้วรู้จัก widget กับเหตุการณ์ขั้นต่อไปที่หน้าจอควบคุมจริงใช้

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

  1. เติมหกช่องว่างใน practice/s04_touch_panel.py จนแผง LED สามสีทำงานบนบอร์ดหรือ Emulator โดยไฟสถานะบนจอ ตัวเลข “ติดอยู่ n จาก 3” และบรรทัดสถานะตรงกับหลอดจริงในทุกกรณีที่ทดสอบ รวมทั้งหลัง “ปิดทั้งหมด”
  2. อธิบายได้ว่าทำไมแผงควบคุมจริงแยกปุ่มเปิดกับปุ่มปิด (คำสั่งที่กดซ้ำแล้วได้ผลเดิม) ทำไมทุกเส้นทางที่เปลี่ยนไฟต้องผ่าน set_led() ที่เดียว และทำไมเก็บสถานะไว้ใน led_on[] แทนการอ่านกลับจาก gpio.led(n).value()
  3. เลือก widget หรือชนิดเหตุการณ์ให้ตรงกับคำถามของหน้าจอได้ เช่น ui.Bar วางทับ ui.Scale เพื่อบอกพิสัย ui.Led เพื่อบอกสถานะ ui.Tabview เมื่อหน้าเดียวไม่พอ และ pressed / long_pressed_repeat / released ที่ขอด้วย .listen() เมื่อต้องรู้ว่านิ้วยังกดค้างอยู่

ต่อจากบทเรียน 2.5 เปิด 03_switch_matches_led.py ของบทเรียนนั้นไว้ข้าง ๆ เพราะ set_led() ในบทเรียนนี้คือแบบแผน “ฟังก์ชันเดียวที่มีสิทธิ์เปลี่ยนสถานะ” ชุดเดียวกัน ก่อนเขียนโค้ด อ่านตาราง “กับดักที่เจอบ่อย” ในสไลด์ ห้าแถวแรกคือกฎเหล็กห้าข้อของ ui ในรูปอาการจริงที่จะเจอ บนจอบอร์ดแตะการ์ด BENTO Playground ค้างไว้ และเตรียมกล้องมือถือไว้ถ่ายจอกับหลอดไฟในเฟรมเดียว

โจทย์ของบทเรียนนี้ไม่ใช่ “กดแล้วไฟติด” แต่คือ จอกับไฟจริงต้องตรงกันเสมอ ความจริงของโปรแกรมอยู่ที่ list led_on[] ที่เดียว จอเป็นแค่ผู้รายงาน เราไม่อ่านสถานะกลับจาก gpio.led(n).value() เพราะมันตอบระดับของขา ไม่ใช่สิ่งที่เราสั่ง และหลัง hold() หรือ brightness() ค่าที่ได้เป็น 0 ทั้งที่ไฟอาจยังติดอยู่

แผงควบคุมจริงแยก ปุ่มเปิด กับ ปุ่มปิด เสมอ ปุ่มสลับบอกไม่ได้ว่าตอนนี้อยู่สถานะไหน คนกดต้องเดา set_led(i, True) จึงเป็นคำสั่ง “เปิด” ไม่ใช่ “สลับ” กดซ้ำสิบครั้งได้ผลเท่ากดครั้งเดียว (idempotent) ไฟสถานะ ui.Led แยกจากปุ่ม เพราะไฟตอบว่า “ตอนนี้เป็นยังไง” ส่วนปุ่มตอบว่า “สั่งอะไรได้” และ .value(0) ทำให้ไฟหรี่ ไม่ใช่หาย คนดูจึงแยกออกว่าไฟดับหรือจอเสีย

ทุกเส้นทางที่เปลี่ยนไฟต้องผ่าน set_led() ซึ่งทำสี่อย่างตามลำดับ คือจำลง led_on[i] สั่งหลอดจริงด้วย gpio.led(LED_IDX[i]) ให้ไฟบนจอสะท้อนของจริง แล้วเรียก show_status() ตอนเริ่มโปรแกรมเราสั่งให้ทุกดวงดับจริง ไม่สมมติว่าดับอยู่แล้ว เลขดวงใน LED_IDX ถามจากชื่อใน gpio.board_info()["led_names"] เพราะ Eva Kit กับ Dev Kit เรียงดวงไม่เหมือนกัน (บน Dev Kit สีน้ำเงินคือดวง 3 และดวง 0-1 อยู่บนโมดูล)

“ปิดทั้งหมด” เปลี่ยนของจริงสามชิ้นในการแตะครั้งเดียว จึงต้องเปิดกล่องยืนยันก่อน กล่องกับปุ่มคำตอบสองปุ่มถูกสร้างพร้อมหน้าจอ แล้วซ่อนไว้ ไม่ได้สร้างตอนคนกด และคำยืนยันบอกสิ่งที่จะเกิด (“ไฟทุกสีจะดับพร้อมกัน”) ไม่ใช่ถามลอย ๆ ว่าแน่ใจไหม ทั้งหน้าใช้ 22 widget ต่ำกว่างบของคอร์ส 32 (เพดานเฟิร์มแวร์ 64) นับบนกระดาษก่อนพิมพ์โค้ดทุกครั้ง

ครึ่งหลังของสไลด์แนะนำของขั้นต่อไป:

  • ui.Scale คือไม้บรรทัด ไม่รับ .value() ตัวที่ขยับคือ ui.Bar ที่วางทับ ส่วน ui.Spinbox ใช้ป้อนค่าที่แถบเลื่อนป้อนไม่ได้ เช่น 23.75
  • ui.Table จัดคอลัมน์ให้เอง จะแก้ค่าในแถวเดิมใช้ .cell() ไม่ใช่ .add_row() และ widget ที่ .hide() ไว้ยังกินโควตา มีแต่ .delete() ที่คืน
  • ui.Tabview แบ่งพื้นที่เป็นหลายหน้า แท็บสามใบกิน 4 แฮนเดิล value= ของมันคือความสูงแถบแท็บ และของที่ต้องเห็นตลอดเวลาห้ามอยู่ในแท็บ ส่วน parent= ทำให้ x=0, y=0 ของลูกคือมุมของกล่อง ไม่ใช่มุมจอ
  • clicked มาถึงตอนปล่อยนิ้วแล้ว ถ้าต้องรู้ว่ายังกดค้างอยู่ใช้ pressed long_pressed_repeat released ซึ่งต้องขอด้วย .listen() ก่อน เพราะคิวเหตุการณ์มีแค่ 16 ช่อง และเมื่อคิวเต็ม ของใหม่คือตัวที่ถูกทิ้ง

ไฟล์ 07–17 ไม่ต้องเปิดครบในบทเรียน ทำแผงให้ผ่านเกณฑ์ก่อน แล้วเปิดตามอาการที่เจอ ก่อนรันแต่ละไฟล์ให้ทายก่อนว่าจอจะแสดงอะไร แล้วค่อยรันเทียบ

  • อยากย้าย ย่อ ซ่อน หรือลบ widget ที่สร้างไปแล้ว: 07 (ui.list() ใช้คีย์ id ส่วน ui.poll() ใช้คีย์ handle)
  • ต้องใช้ช่องเลือกหรือช่องพิมพ์: 08 (Dropdown ส่งกลับแค่ลำดับของตัวเลือก เริ่มที่ 0 ไม่ได้ส่งข้อความ)
  • หน้าจอต้องบอกพิสัย สถานะ และรับค่าที่ผู้ใช้ป้อน: 09 ซึ่งบทเรียน 2.7–2.9 ใช้ต่อทันที
  • ของที่ต้องโชว์เกินหนึ่งหน้า: 10 (แท็บ) และ 11 (เมนูเป็นชั้น) แล้วต่อด้วย 14 เรื่องระบบพิกัดของกล่อง
  • หลายค่าพร้อมกัน หรือคำสั่งที่ต้องถามก่อน: 12 และ 13
  • ต้องรู้ว่านิ้วยังกดค้าง ปัด หรือกำลังเลื่อนรายการ: 15 และ 16

ไฟล์ 17 เป็นส่วนขยายที่ไม่อยู่ในเกณฑ์ผ่าน ต้องแก้ TEAM = "teamXX" เป็นรหัสที่ไม่ซ้ำใครก่อน เช่น nok4821 (ค่าตั้งต้นจงใจให้ไม่ยอมรัน) และต้องมี WiFi ที่ออกพอร์ต 1883 ได้ พอร์ตนี้ไม่เข้ารหัสและ broker เป็นของสาธารณะ ห้ามส่งอะไรที่เป็นความลับ

ไฟล์ ไฟล์นี้สอน
examples/07_find_move_hide_delete.py จัดการ widget ที่สร้างไปแล้ว
examples/08_dropdown_textarea.py อีกสามชนิดที่รับอินพุตได้ และค่าที่ถามกลับได้จริง
examples/09_scale_led_spinbox.py สาม widget ที่แยกหน้าจอ HMI ออกจากหน้าจอเล่น ๆ
examples/10_tabview_second_screen.py จอที่สองที่ไม่ต้องเขียนโปรแกรมที่สอง
examples/11_menu_settings_tree.py จอที่สองแบบที่สอง เมื่อของไม่ได้เท่ากันทุกหัวข้อ
examples/12_table_and_list.py หลายค่าพร้อมกัน ต้องเป็นตาราง ไม่ใช่ป้ายเรียงกัน
examples/13_confirm_before_acting.py คำสั่งที่ทำให้ของจริงขยับ ต้องถามก่อนหนึ่งครั้ง
examples/14_container_coordinates.py กล่องที่คอนเทนเนอร์คืนมา และระบบพิกัดของมัน
examples/15_press_and_hold.py ปุ่มที่ต้องกดค้าง และเหตุการณ์ที่ clicked บอกไม่ได้
examples/16_swipe_scroll_focus.py ปัดนิ้ว เลื่อนรายการ และช่องไหนกำลังถูกเลือก
examples/17_panel_to_broker.py แตะบนจอบอร์ด แล้วหน้าเว็บของเพื่อนเห็น สั่งจากเว็บ แล้วแถบบนจอขยับ

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

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

จอของ examples/07_find_move_hide_delete.py ขณะรันใน BENTO Emulator: จัดการ widget ที่สร้างไปแล้ว
07_find_move_hide_delete.py จัดการ widget ที่สร้างไปแล้ว
จอของ examples/08_dropdown_textarea.py ขณะรันใน BENTO Emulator: อีกสามชนิดที่รับอินพุตได้ และค่าที่ถามกลับได้จริง
08_dropdown_textarea.py อีกสามชนิดที่รับอินพุตได้ และค่าที่ถามกลับได้จริง
จอของ examples/09_scale_led_spinbox.py ขณะรันใน BENTO Emulator: สาม widget ที่แยกหน้าจอ HMI ออกจากหน้าจอเล่น ๆ
09_scale_led_spinbox.py สาม widget ที่แยกหน้าจอ HMI ออกจากหน้าจอเล่น ๆ
จอของ examples/10_tabview_second_screen.py ขณะรันใน BENTO Emulator: จอที่สองที่ไม่ต้องเขียนโปรแกรมที่สอง
10_tabview_second_screen.py จอที่สองที่ไม่ต้องเขียนโปรแกรมที่สอง
จอของ examples/11_menu_settings_tree.py ขณะรันใน BENTO Emulator: จอที่สองแบบที่สอง เมื่อของไม่ได้เท่ากันทุกหัวข้อ
11_menu_settings_tree.py จอที่สองแบบที่สอง เมื่อของไม่ได้เท่ากันทุกหัวข้อ
จอของ examples/12_table_and_list.py ขณะรันใน BENTO Emulator: หลายค่าพร้อมกัน ต้องเป็นตาราง ไม่ใช่ป้ายเรียงกัน
12_table_and_list.py หลายค่าพร้อมกัน ต้องเป็นตาราง ไม่ใช่ป้ายเรียงกัน
จอของ examples/13_confirm_before_acting.py ขณะรันใน BENTO Emulator: คำสั่งที่ทำให้ของจริงขยับ ต้องถามก่อนหนึ่งครั้ง
13_confirm_before_acting.py คำสั่งที่ทำให้ของจริงขยับ ต้องถามก่อนหนึ่งครั้ง
จอของ examples/14_container_coordinates.py ขณะรันใน BENTO Emulator: กล่องที่คอนเทนเนอร์คืนมา และระบบพิกัดของมัน
14_container_coordinates.py กล่องที่คอนเทนเนอร์คืนมา และระบบพิกัดของมัน
จอของ examples/15_press_and_hold.py ขณะรันใน BENTO Emulator: ปุ่มที่ต้องกดค้าง และเหตุการณ์ที่ clicked บอกไม่ได้
15_press_and_hold.py ปุ่มที่ต้องกดค้าง และเหตุการณ์ที่ clicked บอกไม่ได้
จอของ examples/16_swipe_scroll_focus.py ขณะรันใน BENTO Emulator: ปัดนิ้ว เลื่อนรายการ และช่องไหนกำลังถูกเลือก
16_swipe_scroll_focus.py ปัดนิ้ว เลื่อนรายการ และช่องไหนกำลังถูกเลือก
จอของ examples/17_panel_to_broker.py ขณะรันใน BENTO Emulator: แตะบนจอบอร์ด แล้วหน้าเว็บของเพื่อนเห็น สั่งจากเว็บ แล้วแถบบนจอขยับ
17_panel_to_broker.py แตะบนจอบอร์ด แล้วหน้าเว็บของเพื่อนเห็น สั่งจากเว็บ แล้วแถบบนจอขยับ

ไฟล์ฝึกมีช่องว่าง # เติม: หกจุด เรียงตามลำดับที่โปรแกรมทำงานจริง

  • จุด 1: สร้างปุ่ม “เปิด” ของแต่ละแถวด้วย btn_on.append(ui.Button("เปิด", ...))
  • จุด 2–3: ใน set_led() สั่งหลอดจริงด้วย gpio.led(LED_IDX[i]).on() และ .off()
  • จุด 4–5: ในลูปเหตุการณ์ แปลง handle กลับเป็นแถวด้วย set_led(on_ids.index(h), True) และ set_led(off_ids.index(h), False)
  • จุด 6: time.sleep_ms(50) ท้ายลูป

เติมทีละจุดแล้วส่งขึ้นบอร์ดทุกครั้ง อย่าเติมครบหกจุดแล้วรันทีเดียว เพราะจะไม่รู้ว่าพังที่จอ ที่ไฟ หรือที่ตรรกะ หลังเติมจุด 2–3 ลองเรียก set_led(0, True) บรรทัดเดียวก่อนลูป ถ้าไฟติด ฝั่งฮาร์ดแวร์เรียบร้อยแล้ว

ไฟล์ฝึก เรื่อง
practice/s04_touch_panel.py แผงควบคุม LED บนจอสัมผัส (ฉบับฝึกเติมโค้ด)

เปิดเฉลยหลังจากลองเองแล้วอย่างน้อยหนึ่งรอบ แล้วอ่าน วิธีใช้เฉลย ก่อน

เฉลย คู่กับ
solution/s04_touch_panel.py practice/s04_touch_panel.py

คำถามชุดเดียวกันอยู่ใน quiz.yaml สำหรับระบบที่ตรวจอัตโนมัติ

  1. ในเฉลย ปุ่ม “เปิด” แถวแดงเรียก set_led(0, True) แทนคำสั่งที่สลับสถานะไฟ ข้อดีหลักของการออกแบบนี้คืออะไร (เลือกหนึ่งข้อ · เป้าหมายข้อ 2)

    • ก) กดซ้ำกี่ครั้งก็ได้ผลเดิม คนที่กดซ้ำเพราะไม่แน่ใจจะไม่ได้ผลตรงข้ามกับที่ตั้งใจ
    • ข) ใช้ widget น้อยกว่าปุ่มสลับ
    • ค) ไม่ต้องเรียก ui.poll() ในลูปอีก
    • ง) ไฟติดเร็วขึ้นเพราะไม่ต้องผ่านคิวเหตุการณ์
    เฉลย

    ก — คำสั่งที่ระบุปลายทาง (idempotent) ได้ผลเดิมไม่ว่ากดกี่ครั้ง ต่างจากคำสั่งสลับที่ผลขึ้นกับสถานะก่อนหน้าซึ่งคนกดมองไม่เห็น ส่วนเรื่องจำนวน widget ปุ่มแยกกลับใช้มากกว่าด้วยซ้ำ

  2. ทีมหนึ่งให้ show_status() นับดวงที่ติดโดยอ่าน gpio.led(n).value() แล้วพบว่าไฟดวงที่สามติดอยู่แต่จอบอกว่าดับ ข้อใดคือวิธีแก้ที่ถูก (เลือกหนึ่งข้อ · เป้าหมายข้อ 2)

    • ก) เก็บสถานะที่สั่งไว้ใน led_on[] ของเราเอง แล้วให้จอรายงานจากตรงนั้น
    • ข) อ่าน gpio.led(n).value() สองครั้งติดกันแล้วใช้ค่าหลัง
    • ค) เพิ่ม time.sleep_ms(200) ก่อนอ่านค่า
    • ง) อ่านสถานะจากสีของปุ่มบนจอแทน
    เฉลย

    ก — gpio.led(n).value() ตอบระดับของขา ไม่ใช่สิ่งที่เราสั่ง และคืน 0 หลัง hold() หรือ brightness() ความจริงของโปรแกรมจึงต้องอยู่ที่ led_on[] ที่เดียว แล้วให้จอเป็นผู้รายงาน

  3. เรียงสิ่งที่ set_led(i, on) ในเฉลยทำ ให้ถูกลำดับ (เรียงลำดับ · เป้าหมายข้อ 1)

    • ก) สั่งหลอดจริงด้วย gpio.led(LED_IDX[i])
    • ข) เรียก show_status() ให้ตัวเลข “ติดอยู่ n จาก 3” ตรง
    • ค) จำค่าลง led_on[i]
    • ง) ให้ไฟสถานะบนจอ lamps[i] สะท้อนของจริง
    เฉลย

    ค → ก → ง → ข — จำไว้ก่อน สั่งของจริง ให้ไฟบนจอสะท้อน แล้วรายงานให้ครบ เมื่อทุกเส้นทางที่เปลี่ยนไฟผ่านประตูนี้ประตูเดียว จอก็ไม่มีทางพูดไม่ตรงกับหลอด

  4. ทีมที่ถือ Dev Kit เขียน LED_IDX = [0, 1, 2] ตายตัว แตะ “เปิด” แถวน้ำเงินแล้วไม่เห็นหลอดสีน้ำเงินติด สาเหตุคืออะไร (เลือกหนึ่งข้อ · เป้าหมายข้อ 1)

    • ก) สองบอร์ดเรียงดวงไม่เหมือนกัน บน Dev Kit สีน้ำเงินคือดวง 3 และดวง 0-1 อยู่บนโมดูล ต้องถามเลขดวงจากชื่อใน gpio.board_info()[“led_names”]
    • ข) Dev Kit ไม่มีหลอดสีน้ำเงินเลย
    • ค) หลอดจริงจะติดก็ต่อเมื่อ ui.Led บนจอถูกตั้ง value=1 ก่อน
    • ง) ปุ่มแถวที่สามส่งเหตุการณ์ toggled ไม่ใช่ clicked
    เฉลย

    ก — Eva Kit กับ Dev Kit เรียงดวงไม่เหมือนกัน เฉลยจึงให้ LED_IDX มาจากชื่อ RGB_RED / RGB_GREEN / RGB_BLUE ที่บอร์ดรายงาน แทนเลขดวงที่จำมา ทุกปุ่มในแผงนี้ส่ง clicked เหมือนกันหมด

  5. ข้อใดถูกเกี่ยวกับ widget และเหตุการณ์ขั้นต่อไปในบทเรียนนี้ เลือกทุกข้อที่ถูก (เลือกได้หลายข้อ · เป้าหมายข้อ 3)

    • ก) ui.Scale ไม่รับ .value() ตัวที่ขยับคือ ui.Bar ที่วางทับ
    • ข) value= ของ ui.Tabview คือความสูงของแถบแท็บ
    • ค) .hide() คืนโควตา widget ให้ทันที
    • ง) clicked มาถึงตั้งแต่นิ้วแตะลง จึงใช้ทำปุ่มเร่งระหว่างกดค้างได้
    • จ) long_pressed_repeat ต้องขอด้วย .listen() ก่อน ไม่งั้นปุ่มจะไม่ส่งเลย
    เฉลย

    ก, ข, จ — Scale คือไม้บรรทัด Tabview ใช้ value= เป็นความสูงแถบแท็บ และเหตุการณ์กดค้างเงียบจนกว่าจะขอด้วย .listen() ส่วน .hide() ยังกินโควตา (มีแต่ .delete() ที่คืน) และ clicked มาถึงตอนปล่อยนิ้วแล้ว

MVP ของชุดบทเรียน 2.4–2.6 ทำบนบอร์ดจริงหรือ Emulator แล้วจดผลลงบันทึกการเรียน

  • จอมีสามแถว แถวละไฟสถานะ ปุ่ม “เปิด” และปุ่ม “ปิด” การ์ดขวามีตัวเลข “ติดอยู่ n จาก 3 ดวง” ปุ่ม “เปิดทั้งหมด” และ “ปิดทั้งหมด” และมีบรรทัดสถานะข้างหัวเรื่อง
  • แตะ “เปิด” แถวไหน หลอดไฟจริงบนบอร์ดติดสีนั้น แตะ “ปิด” แล้วดับ และกด “เปิด” ซ้ำสิบครั้งได้ผลเท่ากดครั้งเดียว
  • “เปิดทั้งหมด” ติดครบสามสี “ปิดทั้งหมด” เปิดกล่องยืนยันก่อน ยืนยันแล้วดับหมด และกด “ไม่ปิด” แล้วไฟไม่เปลี่ยน
  • ไฟสถานะบนจอ ตัวเลข “ติดอยู่ n จาก 3” และบรรทัดสถานะ ตรงกับหลอดจริงในทุกกรณีที่ทดสอบ รวมทั้งหลัง “ปิดทั้งหมด” (ข้อนี้ตกกันมากที่สุด ทดสอบเป็นข้อสุดท้ายเสมอ)
  • ถ่ายรูปหรือคลิปที่เห็นทั้งจอและหลอดไฟในเฟรมเดียวกัน

การบ้านของทีม: เลือกทำหนึ่งในสี่ข้อต่อยอดในสไลด์ คือปุ่ม START/STOP ของไฟวิ่งด้วยตัวแปร flag, ตัวนับการกดกับปุ่ม RESET, สวิตช์ LOCK ที่ทำให้ปุ่มทั้งหมดกดไม่ได้ หรือปุ่มจริง gpio.button(0) ที่ทำงานร่วมกับปุ่มบนจอโดยระวัง debounce ทั้งสี่ข้อเดินบน event loop เดิม ห้ามหลับรอด้วย sleep จนแตะปุ่มอื่นไม่ได้ และบน Dev Kit ห้ามโยกสวิตช์บนฐาน เพราะหลายตัวคือสวิตช์ตัดไฟ บทเรียน 2.7 กลับทิศจาก “จอสั่งฮาร์ดแวร์” เป็นลูกบิดกับแผ่นสัมผัสที่สั่งจอ

บทเรียนถัดไป: บทเรียน 2.7 — อนาล็อกและสัมผัส: ADC ลูกบิด และ CapSense

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

คำถามทบทวน

ลองตอบเองก่อน แล้วค่อยเปิดดูเฉลย

  1. ในเฉลย ปุ่ม "เปิด" แถวแดงเรียก set_led(0, True) แทนคำสั่งที่สลับสถานะไฟ ข้อดีหลักของการออกแบบนี้คืออะไร (เป้าหมายข้อ 2)

    1. กดซ้ำกี่ครั้งก็ได้ผลเดิม คนที่กดซ้ำเพราะไม่แน่ใจจะไม่ได้ผลตรงข้ามกับที่ตั้งใจ
    2. ใช้ widget น้อยกว่าปุ่มสลับ
    3. ไม่ต้องเรียก ui.poll() ในลูปอีก
    4. ไฟติดเร็วขึ้นเพราะไม่ต้องผ่านคิวเหตุการณ์
    ดูเฉลย

    คำตอบ: A. กดซ้ำกี่ครั้งก็ได้ผลเดิม คนที่กดซ้ำเพราะไม่แน่ใจจะไม่ได้ผลตรงข้ามกับที่ตั้งใจ

    คำสั่งที่ระบุปลายทาง (idempotent) ได้ผลเดิมไม่ว่ากดกี่ครั้ง ต่างจากคำสั่งสลับที่ผลขึ้นกับสถานะก่อนหน้าซึ่งคนกดมองไม่เห็น ส่วนเรื่องจำนวน widget ปุ่มแยกกลับใช้มากกว่าด้วยซ้ำ

  2. ทีมหนึ่งให้ show_status() นับดวงที่ติดโดยอ่าน gpio.led(n).value() แล้วพบว่าไฟดวงที่สามติดอยู่แต่จอบอกว่าดับ ข้อใดคือวิธีแก้ที่ถูก (เป้าหมายข้อ 2)

    1. เก็บสถานะที่สั่งไว้ใน led_on[] ของเราเอง แล้วให้จอรายงานจากตรงนั้น
    2. อ่าน gpio.led(n).value() สองครั้งติดกันแล้วใช้ค่าหลัง
    3. เพิ่ม time.sleep_ms(200) ก่อนอ่านค่า
    4. อ่านสถานะจากสีของปุ่มบนจอแทน
    ดูเฉลย

    คำตอบ: A. เก็บสถานะที่สั่งไว้ใน led_on[] ของเราเอง แล้วให้จอรายงานจากตรงนั้น

    gpio.led(n).value() ตอบระดับของขา ไม่ใช่สิ่งที่เราสั่ง และคืน 0 หลัง hold() หรือ brightness() ความจริงของโปรแกรมจึงต้องอยู่ที่ led_on[] ที่เดียว แล้วให้จอเป็นผู้รายงาน

  3. เรียงสิ่งที่ set_led(i, on) ในเฉลยทำ ให้ถูกลำดับ (เป้าหมายข้อ 1)

    1. สั่งหลอดจริงด้วย gpio.led(LED_IDX[i])
    2. เรียก show_status() ให้ตัวเลข "ติดอยู่ n จาก 3" ตรง
    3. จำค่าลง led_on[i]
    4. ให้ไฟสถานะบนจอ lamps[i] สะท้อนของจริง
    ดูเฉลย

    ลำดับที่ถูก: C. จำค่าลง led_on[i] → A. สั่งหลอดจริงด้วย gpio.led(LED_IDX[i]) → D. ให้ไฟสถานะบนจอ lamps[i] สะท้อนของจริง → B. เรียก show_status() ให้ตัวเลข "ติดอยู่ n จาก 3" ตรง

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

  4. ทีมที่ถือ Dev Kit เขียน LED_IDX = [0, 1, 2] ตายตัว แตะ "เปิด" แถวน้ำเงินแล้วไม่เห็นหลอดสีน้ำเงินติด สาเหตุคืออะไร (เป้าหมายข้อ 1)

    1. สองบอร์ดเรียงดวงไม่เหมือนกัน บน Dev Kit สีน้ำเงินคือดวง 3 และดวง 0-1 อยู่บนโมดูล ต้องถามเลขดวงจากชื่อใน gpio.board_info()["led_names"]
    2. Dev Kit ไม่มีหลอดสีน้ำเงินเลย
    3. หลอดจริงจะติดก็ต่อเมื่อ ui.Led บนจอถูกตั้ง value=1 ก่อน
    4. ปุ่มแถวที่สามส่งเหตุการณ์ toggled ไม่ใช่ clicked
    ดูเฉลย

    คำตอบ: A. สองบอร์ดเรียงดวงไม่เหมือนกัน บน Dev Kit สีน้ำเงินคือดวง 3 และดวง 0-1 อยู่บนโมดูล ต้องถามเลขดวงจากชื่อใน gpio.board_info()["led_names"]

    Eva Kit กับ Dev Kit เรียงดวงไม่เหมือนกัน เฉลยจึงให้ LED_IDX มาจากชื่อ RGB_RED / RGB_GREEN / RGB_BLUE ที่บอร์ดรายงาน แทนเลขดวงที่จำมา ทุกปุ่มในแผงนี้ส่ง clicked เหมือนกันหมด

  5. ข้อใดถูกเกี่ยวกับ widget และเหตุการณ์ขั้นต่อไปในบทเรียนนี้ เลือกทุกข้อที่ถูก (เป้าหมายข้อ 3)

    1. ui.Scale ไม่รับ .value() ตัวที่ขยับคือ ui.Bar ที่วางทับ
    2. value= ของ ui.Tabview คือความสูงของแถบแท็บ
    3. .hide() คืนโควตา widget ให้ทันที
    4. clicked มาถึงตั้งแต่นิ้วแตะลง จึงใช้ทำปุ่มเร่งระหว่างกดค้างได้
    5. long_pressed_repeat ต้องขอด้วย .listen() ก่อน ไม่งั้นปุ่มจะไม่ส่งเลย
    ดูเฉลย

    คำตอบ: A. ui.Scale ไม่รับ .value() ตัวที่ขยับคือ ui.Bar ที่วางทับ · B. value= ของ ui.Tabview คือความสูงของแถบแท็บ · E. long_pressed_repeat ต้องขอด้วย .listen() ก่อน ไม่งั้นปุ่มจะไม่ส่งเลย

    Scale คือไม้บรรทัด Tabview ใช้ value= เป็นความสูงแถบแท็บ และเหตุการณ์กดค้างเงียบจนกว่าจะขอด้วย .listen() ส่วน .hide() ยังกินโควตา (มีแต่ .delete() ที่คืน) และ clicked มาถึงตอนปล่อยนิ้วแล้ว

อ้างอิงบทเรียนนี้

ถ้านำบทเรียนนี้ไปสอน ทำสไลด์ หรือทำเอกสารต่อ ให้อ้างอิงด้วยข้อความนี้ ถ้าดัดแปลงเนื้อหา ให้เติม (ดัดแปลง)ต่อท้ายชื่อบทเรียน

"ลงมือทำ: แผงควบคุมสัมผัสของเรา และ widget ขั้นต่อไป" จาก TESA Open Knowledge โดยสมาคมสมองกลฝังตัวไทย (Thai Embedded Systems Association: TESA) https://github.com/tesaiot/tesa-qualification-program สัญญาอนุญาต CC BY-NC 4.0

ข้อความอ้างอิงภาษาอังกฤษ: "Hands-on: your touch control panel and the next widgets" 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/l06-touch-panel-lab/

บทเรียนนี้ดัดแปลงจากต้นฉบับด้านล่าง เมื่ออ้างอิงให้คงเครดิตต้นฉบับไว้ด้วย
https://github.com/Advance-Innovation-Centre-AIC/embedded-systems-for-aiot-developer/blob/a80bbe88a34bcb9bb8d991f42f9252b77cdab079/session-04.html (slides 36–68)

วิธีอ้างอิง TESA ฉบับเต็ม

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

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