| คำถาม | คำตอบของชุดบทเรียนนี้ | อยู่ช่วงไหน | |
|---|---|---|---|
| Why | บทเรียน 2.1–2.3 กดปุ่มจริงก็สั่งไฟได้แล้ว ทำไมต้องมีปุ่มบนจออีก | เพราะเครื่องที่ส่งมอบไปแล้ว คนอื่นเป็นคนใช้ และเขาเห็นแค่จอ · ทั้งสองบอร์ดมีปุ่มจริงให้ Python แตะได้ปุ่มเดียว แต่คำสั่งของเครื่องหนึ่งเครื่องมีมากกว่าหนึ่งคำสั่ง · และแผงที่รายงานไม่ตรงกับของจริง คือแผงที่หลอกคนคุมเครื่อง | ครึ่งแรก · สไลด์ "สถานะบนจอ กับ สถานะจริง" และ "ใช้จริงที่ไหน" |
| What | มีอะไรให้ใช้บ้าง | โมดูล ui ทั้ง 132 ชื่อ (นับบน Eva Kit — Dev Kit มี Sprite เพิ่ม) — ตัวสร้าง widget 33 · ฟังก์ชันระดับโมดูล 8 · เสียง 2 กับค่าคงที่ของเสียงอีก 25 · ค่าคงที่อื่นอีก 63 · ชนิด Widget 1 · บวกเมธอดของ Widget อีก 38 ตัว |
สไลด์ฝั่งอินพุต + สไลด์บัญชี 132 ชื่อ |
| How | ประกอบยังไงให้ใช้งานได้จริง | event loop ที่เรียก ui.poll() ทุกรอบ แยกว่าเหตุการณ์มาจากใครด้วย handle แล้วสั่ง gpio.led() พร้อมเขียนบรรทัดสถานะกลับในจังหวะเดียวกัน |
17 ไฟล์ตัวอย่าง (01–06 ในบทเรียน 2.5 · 07–17 ในบทเรียน 2.6) + ไฟล์ฝึกสองไฟล์ |
ปลายทางที่จับต้องได้ — แผงควบคุม LED สามสี แถวละ ไฟสถานะ + ปุ่มเปิด + ปุ่มปิด พร้อม "เปิดทั้งหมด" และ "ปิดทั้งหมด" ที่ถามยืนยันก่อน แตะแล้วหลอดจริงบนบอร์ดเปลี่ยนตาม พร้อมตัวเลขสรุปและบรรทัดสถานะที่ไม่เคยโกหก
บทเรียน 2.1–2.3 เราเป็นคนสั่งไฟเอง · ชุดบทเรียนนี้เราเปิดให้คนอื่นสั่งได้ โดยที่เรายังรับผิดชอบว่าจอพูดความจริง
ui.Button, ui.Led, ui.Label, ui.MsgBox วางตำแหน่งเองได้ และรู้ว่าเมื่อไรควรปล่อยให้จอจัดวางให้.id() ไว้ui.poll() ที่แยกได้ว่าเหตุการณ์ไหนมาจาก widget ตัวไหนgpio.led() แล้วทำให้ สถานะบนจอตรงกับหลอดไฟจริงตลอดเวลาปลายทางของวันนี้: แผงควบคุม LED สามสี แถวละ ไฟสถานะ + เปิด + ปิด และคำสั่งทั้งชุดที่ยืนยันก่อน ใช้งานได้จริง และมีตัวเลขสรุปกับบรรทัดสถานะที่ไม่เคยโกหก
ชุดบทเรียนนี้ยากกว่าบทเรียน 2.1–2.3 ตรงที่ของสองฝั่ง (จอกับหลอด) ต้องพูดตรงกัน ไม่ใช่แค่ทำงานได้

set_led() ทำงาน ไม่ใช่ข้อความที่พิมพ์ต่อท้ายไปเรื่อย ๆตั้งแต่ชุดบทเรียนนี้ไป จอไม่ใช่แค่ที่พิมพ์ข้อความออก แต่เป็นทางที่คนสั่งงานเข้ามา
บทเรียน 2.1–2.3 จบที่ 07_button_to_broker.py กดปุ่มผู้ใช้บนบอร์ดหนึ่งครั้ง ได้ event หนึ่งใบที่ bento-aiot/team03/event · สถานะไฟเดินทางไปกับ telemetry เป็นจังหวะ · และหน้าเว็บ mqtt_dashboard.html ส่ง cmd กลับมาจุดไฟบนโต๊ะเราได้
| บทเรียน 2.1–2.3 | บทเรียน 2.4–2.6 วันนี้ |
|---|---|
| ปุ่มจริงปุ่มเดียว อ่านด้วยการวนถาม และต้องกันเด้งเอง | ปุ่มบนจอกี่ปุ่มก็ได้ มาเป็นเหตุการณ์ทีละรายการจาก ui.poll() |
| ไฟจริงเปลี่ยนเพราะปุ่มจริง | ไฟจริงเปลี่ยนเพราะนิ้วแตะจอ และจอต้องบอกสถานะ ตรงกับ หลอดเสมอ |
| กดปุ่มจริงแล้วเว็บเห็น | ส่วนขยายท้ายบทเรียน: แตะบนจอแล้วเว็บเห็น และเว็บสั่งให้แถบบนจอขยับ |
โครงลูปยังเป็นตัวเดิม อ่าน → ตัดสินใจ → สั่ง → หน่วง → วนใหม่ เปลี่ยนแค่ว่าอินพุตมาจากนิ้วบนกระจก
ทีมที่ยังไม่ได้ลองไฟล์ 07 ของบทเรียน 2.1–2.3 ไม่เสียอะไร เนื้อหาหลักของวันนี้ไม่พึ่งมัน มันกลับมาอีกครั้งเฉพาะในส่วนขยายท้ายบทเรียน
บทเรียน 2.1–2.3 เราคุมฮาร์ดแวร์ด้วยโค้ดล้วน ๆ — บรรทัดที่ยังใช้ต่อทั้งหมดในชุดบทเรียนนี้ ตัดจาก s03_led_button.py ตรง ๆ
NUM_LEDS = gpio.num_leds()
...
btn = gpio.button(0)
...
for i in range(NUM_LEDS):
gpio.led(i).off()
...
gpio.led(led_index).off() # ดับดวงเดิมก่อน
led_index = (led_index + 1) % NUM_LEDS # เลื่อนไปดวงถัดไป วนกลับที่ 0 เอง
gpio.led(led_index).on() # จุดดวงใหม่
...
raw = btn.is_pressed()
ดัชนีดวงของแต่ละ สี เป็นค่าที่ถามบอร์ด ไม่ใช่เลขที่จำมา — Eva: ดวง 0 แดง 1 เขียว 2 น้ำเงิน (ดวง 2 ชื่อ RGB_RED แต่ส่องน้ำเงิน) · Dev Kit: หาจากชื่อ RGB_RED / RGB_GREEN / RGB_BLUE ใน led_names (เฉลยส่วนที่หนึ่งทำให้ดู) · ปุ่มผู้ใช้มีตัวเดียว .name() คืน "USER Button 1" ไม่ใช่ป้ายบนแผ่นวงจร


gpio.led() วันนี้ คือขา USER_LED1-3 ที่มุมซ้ายของวงจรนี้ (วงจรของ Eva Kit — Dev Kit ยังไม่ได้เปิดคู่มือตรวจ แต่ฝั่งโค้ดสั่งเหมือนกัน) — ขา MCU ขับที่ gate ของ MOSFET ไม่ได้จ่ายกระแสให้หลอดเอง แพทเทิร์นลูปเดิมยังอยู่ครบ: อ่าน input → ตัดสินใจ → สั่ง output → หน่วงเวลา → วนใหม่ ชุดบทเรียนนี้เปลี่ยนแค่แหล่ง input จาก "ปุ่มจริง" เป็น "นิ้วบนกระจก"โครงลูปเดิม แต่ input มาจากคนละโลก — ตรงนี้แหละที่ทำให้ต้องมีกลไกใหม่ชื่อ event
ปุ่มจริงบนบอร์ดต่อสายตรงเข้าขาชิป โค้ดของเราอ่านค่าขาได้ทันทีเมื่อไรก็ได้ — นั่นคือ polling
ปุ่มบนจอไม่มีสาย มันเป็นภาพที่ CM55 วาด และนิ้วเราไปโดนตัวตรวจจับสัมผัสของจอ ซึ่งอยู่ฝั่ง CM55 ทั้งหมด ส่วนโค้ด Python ของเรารันอยู่ที่ CM33 คนละคอร์กัน
CM55 จึงต้อง จดเหตุการณ์ใส่คิวไว้ แล้วรอให้ CM33 มาถามว่า "มีอะไรใหม่ไหม" คำถามนั้นคือ ui.poll()
ปุ่มบนบอร์ดเราไป "อ่าน" เอง ส่วนปุ่มบนจอเราต้องไป "รับของที่ฝากไว้"


ทั้งสองคลิปเป็นของนอกเวลา (ต้องมีอินเทอร์เน็ต) ไม่ต้องเปิดในบทเรียน — เนื้อหาวันนี้เข้าใจได้ครบโดยไม่ต้องดู หลักการเดียวกันนี้กลับมาอีกครั้งในบทเรียน 2.7–2.9 ตอนที่เราอ่านปุ่ม CapSense บนบอร์ดโดยตรง
จอสัมผัสไม่ได้วัด "แรงกด" มันวัด ความใกล้ของตัวนำ — จำประโยคนี้ไว้ ชุดบทเรียนถัดไปใช้ซ้ำ
แพทเทิร์นนี้ยกมาจากคอร์สภาษา C ที่เราเคยสอน — บน MicroPython โค้ดสั้นลงมาก แต่ ลำดับความคิดเหมือนเดิมทุกขั้น
| ขั้น | ในภาษา C (LVGL ดิบ) | ในโค้ดของเรา |
|---|---|---|
| 1 สร้างตัว widget | lv_button_create(parent) |
btn = ui.Button("แดง") |
| 2 กำหนดตำแหน่ง | lv_obj_align(...) |
x=20, y=110 ตอนสร้าง หรือ .pos(x, y) |
| 3 ใส่ข้อความ/หน้าตา | สร้าง label ลูกแล้ว lv_label_set_text |
text="แดง", color=0xE53935 |
| 4 ผูกเหตุการณ์ | lv_obj_add_event_cb(btn, cb, ...) |
เก็บ btn.id() ไว้เทียบใน ui.poll() |
| 5 กรองชนิดเหตุการณ์ | if(code == LV_EVENT_CLICKED) |
if ev['type'] == 'clicked' |
ขั้น 4 ต่างกันที่สุด: C ฝาก callback ไว้ให้ระบบเรียก ส่วนเราเก็บ หมายเลขประจำตัว ไว้เช็กเองในลูป
จำห้าขั้นนี้ให้ขึ้นใจ ทุก widget ที่เหลือในคอร์ส (Slider, Arc, Chart) ใช้ลำดับเดียวกันหมด
import ui
ui.screen() # ล้างของเก่าทั้งหน้าก่อนเริ่มเสมอ
title = ui.Label("แผงควบคุมของทีม", x=20, y=20, color=0xFFFFFF, value=24)
btn = ui.Button("แดง", x=20, y=110, w=170, h=80, color=0xE53935)
sw = ui.Switch(x=620, y=120)
x, y มุมซ้ายบนเป็นพิกเซล · w, h ถ้าไม่ใส่ จอเลือกขนาดพอดีข้อความให้เองcolor สีเป็นเลขฐานสิบหก 0xRRGGBB แบบเดียวกับ CSSx=-1 (ค่าเริ่มต้น) = auto-layout — ปล่อยให้จอเรียงให้ เหมาะกับตอนลองของเร็ว ๆ ไม่เหมาะกับแผงควบคุมแผงควบคุมจริงต้องกำหนด x, y เอง เพราะ "ปุ่มอยู่ตรงไหน" เป็นส่วนหนึ่งของการออกแบบ ไม่ใช่เรื่องบังเอิญ
value= คือขนาดฟอนต์ui.Label("อุณหภูมิ", value=24) # 24 = ตัวอักษรสูง 24 พิกเซล ไม่ใช่ค่า 24 องศา
ui.Button("แดง", value=20) # ตัวหนังสือบนปุ่มขนาด 20
สำหรับ Label, Button, Dropdown, Textarea พารามิเตอร์ value= หมายถึง ขนาดฟอนต์ และรับได้แค่ห้าค่า: 14 / 16 / 20 / 24 / 28
ส่วน min, max, value ที่เป็น "ค่าจริง" ตามชื่อ ใช้กับ Slider, Arc, Bar เท่านั้น (บทเรียน 2.7–2.9 กับ 3.1–3.3 เราจะได้ใช้)
อยากเปลี่ยนข้อความของ Label ให้ใช้เมธอด .text("ข้อความใหม่") เสมอ ไม่ใช่ value=
เห็น
value=ที่ Label เมื่อไร ให้อ่านในใจว่า "ขนาดตัวอักษร" ทันที จะไม่พลาดอีกเลย
ตอนสร้าง widget สิ่งที่เราได้กลับมาคือ ตัวจัดการ (handle) ที่ผูกกับ widget จริงบน CM55 อีกฝั่งหนึ่ง
btn_red = ui.Button("แดง", x=20, y=110, w=170, h=80)
red_id = btn_red.id() # หมายเลขประจำตัว ใช้เทียบตอนรับเหตุการณ์
เหตุการณ์ที่ ui.poll() คืนมาไม่ได้บอกว่า "ปุ่มแดงถูกกด" มันบอกแค่ หมายเลข ว่า widget เบอร์นี้ถูกกด งานของเราคือจำไว้ว่าเบอร์ไหนคือใคร
วิธีที่สะอาดที่สุดเมื่อมีปุ่มหลายตัวคือเก็บเป็น list แล้วใช้ .index() หาลำดับ
on_ids = [b.id() for b in btn_on]
i = on_ids.index(ev['handle']) # ได้ 0, 1 หรือ 2 = แถวไหน (แดง เขียว น้ำเงิน)
ถ้าไม่เก็บ
.id()ไว้ ตอนเหตุการณ์เข้ามาเราจะแยกไม่ออกเลยว่าใครเป็นคนส่ง