ข้อ 1 กับข้อ 2 เป็น กับดักที่ทำให้เสียเวลาทั้งบทเรียนถ้าไม่รู้ก่อน — จะนั่งแก้โค้ดที่ไม่ได้ผิด
ข้อ 3 เป็นเรื่องที่น่าสนใจกว่านั้น: หน้าจอที่ดูน่าเชื่อถือ อาจแสดงค่าที่ไม่ได้มาจากการวัดจริง การรู้ว่า ตัวเลขบนจอมาจากไหน คือส่วนหนึ่งของงานวิศวกรรม ไม่ใช่การจับผิด
ในโค้ดวันนี้เราก็เดาเกตเวย์เหมือนกัน — ต่างกันตรงที่เราจะ ping เพื่อพิสูจน์ ว่าเดาถูก แล้วแสดงผลตามความจริง
wifi ทั้งแปดชื่อ — ตารางที่เปิดค้างไว้ได้ทั้งบทเรียน| เรียกอย่างไร | คืนอะไร | สิ่งที่ต้องรู้ก่อนใช้ |
|---|---|---|
wifi.scan() |
list ของ tuple (ssid, rssi, security, channel) |
คืน ไม่เกิน 20 วง ต่อครั้ง วงที่ 21 หายไปเงียบ ๆ · บล็อก 3-10 วินาที · ถ้าบอร์ดกำลังสแกนของตัวเองอยู่ มันจะรอให้เสร็จก่อนแล้วค่อยถามใหม่ ไม่โยน error ใส่เรา |
wifi.connect(ssid, password) |
True / False |
รับ สองอาร์กิวเมนต์แบบตำแหน่งเท่านั้น · บล็อกได้ถึงราว 85 วินาทีเมื่อรหัสผิด · ล็อกโหมดเป็น WPA3/WPA2 ตายตัว จึง ต่อวงเปิดที่ไม่มีรหัสผ่านจาก Python ไม่ได้ |
wifi.disconnect() |
None |
ตัดลิงก์แล้วบอกคอร์จอให้ลดไอคอนลง · เรียกตอนยังไม่เคยต่อก็ไม่ error เงียบ ๆ ผ่านไป |
wifi.status() |
dict 5 คีย์ mode connected ip ssid rssi |
mode คืน "off" / "idle" / "sta" เป็นช่องเดียวที่บอกมากกว่า is_connected() · แต่ ssid คืนสตริงว่างและ rssi คืน 0 เสมอ ทั้งคู่เป็นค่าตายตัวในเฟิร์มแวร์ |
wifi.is_connected() |
True / False |
ถามสถานะจริงจากไดรเวอร์ทุกครั้ง ไม่ใช่ค่าที่จำไว้ |
wifi.ip() |
str | ยังไม่ต่อจะได้ "0.0.0.0" ซึ่ง เป็นสตริงที่ไม่ว่าง จึงเป็นจริงใน if · ต้องเทียบค่าตรง ๆ ห้ามเขียน if wifi.ip(): |
wifi.ping(host, timeout=5000) |
int มิลลิวินาที หรือ -1 |
รับ เลข IP เท่านั้น ใส่ชื่อโฮสต์ได้ ValueError · ถ้ายังไม่ต่อเน็ตได้ OSError ไม่ใช่ -1 |
wifi.softap(ssid, password) |
True / False |
บอร์ดกลายเป็นตัวปล่อยสัญญาณเองที่ 192.168.4.1 · ไม่ใส่อาร์กิวเมนต์จะได้ชื่อ PSoC-Edge-MPY รหัส micropython · WPA2 ช่อง 1 |
หกแถวแรกคือของที่โครงหลักวันนี้เรียกจริง สองแถวล่างเราจะลองมือในสไลด์ถัดไป
# --- disconnect(): วิธีพิสูจน์ว่า "หลุด" หน้าตาเป็นอย่างไรจริง ๆ ---
wifi.disconnect() # คืน None ไม่ใช่ True
print(wifi.is_connected()) # False
print(wifi.ip()) # "0.0.0.0" <- ไม่ว่าง จึงเป็นจริงใน if
# --- softap(): เมื่อหาวงที่ตั้งไว้ไม่เจอ บอร์ดปล่อยวงของตัวเองได้ ---
wifi.softap("bento-team01", "12345678") # True แล้วบอร์ดอยู่ที่ 192.168.4.1
disconnect() ทำให้เรา สร้างสถานะ "หลุด" ขึ้นมาดูได้ตามสั่ง ไม่ต้องรอให้เน็ตล่มเอง — บทเรียน 5.1–5.3 ต้องเขียนโค้ดที่ทนการหลุด ถ้าไม่เคยเห็นว่าหลุดแล้วค่าไหนเปลี่ยน จะเขียนเงื่อนไขถูกได้ยาก
softap() เป็นทางออกจริงเมื่อไม่มีวงให้ต่อ — เครื่องมือช่างและกล้องติดรถจำนวนมากตั้งค่าครั้งแรกด้วยวิธีนี้ ข้อแลกเปลี่ยนคือ วิทยุมีชุดเดียว เป็น AP แล้วจะเป็นลูกข่ายพร้อมกันไม่ได้ ping() ออกเน็ตจึงใช้ไม่ได้ในโหมดนี้ · ลองไฟล์ 07_disconnect_rejoin.py กับ 08_softap_fallback.py
ui.screen()
time.sleep_ms(200)
ui.Label("สถานะเครือข่ายของทีม", x=24, y=8, color=COL_TEXT, value=24)
l_ssid = ui.Label("SSID: -", x=336, y=12, color=COL_TEXT, value=20)
l_ip = ui.Label("IP: -", x=24, y=48, color=COL_TEXT, value=20)
l_tick = ui.Label("กำลังสแกน", x=336, y=48, color=COL_DIM, value=20)
btn_scan = ui.Button("สแกนใหม่", x=552, y=8, w=216, h=88, color=0x3A4150, value=20)
tbl = ui.Table(x=24, y=104, w=480, h=288, cols=4) # แถวละ 72 px: หัว + 3 แถว = 288
tbl.col_width(0, 144) # กว้างพอสำหรับชื่อ 12 ตัวอักษร ซึ่งเป็นเพดานที่เราตัดไว้
tbl.col_width(1, 96)
tbl.col_width(2, 80)
tbl.col_width(3, 128) # "มีรหัส" คือข้อความที่ยาวที่สุดในคอลัมน์นี้
ทุก widget ถูกสร้าง ก่อน เข้าลูป ในลูปแค่เปลี่ยนข้อความกับค่า · ตารางเดียวแทน ui.Label สิบตัว — col_width คือสิ่งเดียวที่ต้องตั้งให้ถูก ช่องแคบกว่าข้อความ = ตัดบรรทัด = แถวสูงสองเท่า แถวสุดท้ายตกขอบเงียบ ๆ · ปุ่ม สแกนใหม่ อยู่บนแถบหัวเรื่อง เพราะ มุมขวาล่างเฟิร์มแวร์ถือไว้ให้ปุ่ม Console widget ที่ไปทับมันจะถูกบังจนกดไม่โดน
16 widgets จาก 64 — เหลือที่ให้ทีมเติมของตัวเองได้อีกมาก
# --- ท่าที่ 2 + 3 อยู่ในฟังก์ชันเดียว เพราะปุ่ม "สแกนใหม่" ต้องเรียกซ้ำได้ทั้งชุด ---
def rescan():
nets = wifi.scan() # บล็อกราว 3-10 วินาที
nets.sort(key=lambda net: net[1], reverse=True) # net[1] คือ rssi
sort() แก้ลิสต์เดิมในที่ · key=lambda net: net[1] สั่งให้เรียงตามช่องที่สอง · reverse=True ทำให้ค่ามากมาก่อน — RSSI ค่ามากคือแรงกว่า เพราะ −48 มากกว่า −79
ทำไมสองบรรทัดนี้ต้องอยู่ในฟังก์ชัน: ปุ่ม สแกนใหม่ ต้องทำงานทั้งชุด — สแกน เรียง ล้างตาราง เทใหม่ ถ้าปล่อยสองบรรทัดนี้ไว้กลางไฟล์ ปุ่มจะเรียกได้แค่ครึ่งเดียวของงาน
ตัวอย่างที่มากับเฟิร์มแวร์เขียนผิดจริง — ไฟล์ network/01_wifi_scan_connect.py บรรทัด 33 ยังเขียน net['ssid'] อยู่จนวันนี้ บทเรียนคือ โค้ดตัวอย่างไม่ใช่เอกสารอ้างอิง อย่าลอกไปใช้
tbl.clear_items() # ท่าที่ 3 (ยังอยู่ใน rescan)
tbl.add_row("SSID", "dBm", "ช่อง", "รหัส") # หัวสั้น เพราะช่องแคบ
for i in range(min(TOP_N, len(nets))):
ssid, rssi, security, channel = nets[i] # แกะสี่ช่องพร้อมกัน
tbl.add_row(ssid[:12], str(rssi), str(channel),
"เปิด" if security == 0 else "มีรหัส")
ui.poll()
for ssid, rssi, security, channel in nets: # แล้วค่อยขยับมาตรวัดฝั่งขวา
if ssid == WIFI_SSID: # มาตรวัดเล่าเรื่องวงของทีมเอง
pct, col = signal_of(rssi)
bar_rssi.value(rssi) # Bar รับพิสัย -90..-40 ตรง ๆ ได้
l_rssi.color(col)
l_rssi.text("ความแรง " + str(rssi) + " dBm (" + str(pct) + "%)")
— ยืดช่วง −90 ถึง −40 dBm ให้เป็น 0 ถึง 100 เพื่อบอก คุณภาพเป็นเปอร์เซ็นต์ ข้างตัวเลข dBm · ตัวเลขจริง: −67 dBm →
ui.Bar ไม่ต้องแปลงอะไรเลย เพราะตั้ง min=-90, max=-40 ได้ตรง ๆ — ที่ต้องแปลงคือเลขที่ คน จะอ่าน ไม่ใช่เลขที่ widget จะรับ · min(TOP_N, len(nets)) กันพังตอนสแกนเจอน้อยกว่าสามวง
signal_of()คืนทั้งเปอร์เซ็นต์และสีพร้อมกัน เพราะมาจากตัวเลขเดียว — แยกกันคำนวณเมื่อไร วันหนึ่งมันจะไม่ตรงกัน
l_tick.text("กำลังต่อ 85 วิ") # ท่าที่ 4: บอกก่อน แล้วค่อยเรียกของที่บล็อก
ui.poll()
ok = wifi.connect(WIFI_SSID, WIFI_PASS) # สองอาร์กิวเมนต์ตามลำดับเท่านั้น
if not ok:
l_ssid.color(COL_BAD)
l_ssid.text("ต่อไม่ติด")
l_tick.text("ตรวจ SSID/รหัสผ่าน")
ui.poll()
raise SystemExit
...
led_up.value(1) # ต่อติดแล้วไฟดวงบนติด ดวงล่างหรี่
led_down.value(0)
wifi.connect() รับ สองอาร์กิวเมนต์แบบตำแหน่งเท่านั้น — ไม่มี timeout= ไม่มี security= · raise SystemExit เมื่อต่อไม่ติดคือการตัดสินใจที่ตั้งใจ เพราะท่าที่ 5 ไม่มีความหมายถ้าไม่มีลิงก์ — ล้มเร็วดีกว่าปล่อยให้ลูปวนแสดง timeout จนคนดูสับสน · สองบรรทัดสุดท้ายคือ สถานะลิงก์เป็นไฟ ไม่ใช่ตัวอักษรสี — แปลงภาพเป็นขาวดำแล้วตัวอักษรเขียว/แดงกลายเป็นเทาเหมือนกัน ส่วนไฟติดกับไฟหรี่ยังแยกออกด้วยความสว่างและตำแหน่ง
ในระบบฝังตัว ทุกบรรทัดที่บล็อกนานกว่าครึ่งวินาที ต้องมีการแจ้งผู้ใช้กำกับเสมอ
while True: # ท่าที่ 5 — ลูปเดินทุก 200 ms
now = time.ticks_ms()
events = ui.poll() # ลืมบรรทัดนี้ = widget หายใน 2 วิ
for ev in events: # นิ้วมาถึงเมื่อไรก็รับได้ทันที
if ev["type"] == "clicked" and ev["handle"] == btn_scan.id():
l_tick.text("กำลังสแกน") # บอกก่อน แล้วค่อยเรียกของที่บล็อก
ui.poll()
nets = rescan()
if wifi.is_connected():
if time.ticks_diff(now, t_ping) >= PING_EVERY_MS: # ping เดินนาฬิกาตัวเอง
t_ping = now
try:
ms_gw = wifi.ping(gw, PING_TIMEOUT_MS)
ms_net = wifi.ping(NET_TEST_IP, PING_TIMEOUT_MS)
except OSError:
ms_gw, ms_net = -1, -1
l_gw.text(ms_text("เกตเวย์", ms_gw))
time.sleep_ms(200)
ลูปเดินทุก 200 ms แต่ ping เดินตามนาฬิกาของตัวเองทุก 3 วินาที — งานคนละจังหวะอยู่ในลูปเดียวกันได้ ถ้าแต่ละงานถามนาฬิกาเอง แทนที่จะใช้ sleep ยาว ๆ ขวางทาง · ถ้าเขียน sleep_ms(3000) ก้อนเดียว ปุ่มบนจอจะกดแล้วรอถึงสามวินาทีกว่าจะตอบ ซึ่งคนกดจะสรุปว่าปุ่มเสีย
try/except ครอบไว้เพราะ ความผิดพลาดชั่วคราวของเครือข่ายเป็นเรื่องปกติ ไม่ใช่ข้อยกเว้น · ส่วนตัวเลขนับถอยหลัง "วัดใหม่ใน N วิ" เขียนใหม่เมื่อ วินาทีเปลี่ยน เท่านั้น ไม่ใช่ทุกรอบลูป — ตัวเลขที่กระพริบห้าครั้งต่อวินาที คนอ่านไม่ทันและไม่มีใครได้ประโยชน์
ยิงเกตเวย์ก่อนแล้วค่อยยิงอินเทอร์เน็ต — ลำดับนี้ทำให้อ่านผลสองบรรทัดแล้วรู้ทันทีว่าขาดตรงไหน
เวลาดีบัก ให้ถามว่า "ขาดที่ช่วงไหนของห้าช่วงนี้" แล้วปัญหาจะเหลือแค่หนึ่งในห้า ไม่ใช่ทั้งระบบ
โครงหน้าจอถูกวางให้ต่อยอดจากแดชบอร์ดบทเรียน 3.7–3.9 ได้ทันที — ถ้าทีมอยากเอาการ์ด IMU กลับมาวางคู่กัน ให้ลด TOP_N จาก 3 เหลือ 2 แล้วตั้ง h ของตารางเป็น 216 (หัวตารางบวกสองแถว แถวละ 72) พื้นที่ที่ว่างขึ้นมาพอวางการ์ดเดิมได้
ถ้าจอค้างอยู่ที่ กำลังต่อ 85 วิ นานเกินหนึ่งนาทีครึ่ง ให้กด RESTART แล้วตรวจ SSID กับรหัสผ่านทีละตัวอักษร — อย่ารอต่อ
ระหว่างพิมพ์ตามทีละท่า จับเวลาและจดตัวเลขที่เห็นบนจอไว้ด้วย (ถ้าเรียนเป็นกลุ่ม สลับกันพิมพ์กับจด)
อ่านตารางนี้ให้ครบก่อนเริ่มเขียน แล้วผู้เรียนจะประหยัดเวลาไปได้มาก — กับดักเกินครึ่งของชุดบทเรียนนี้อยู่ในคอลัมน์ขวา ไม่ได้อยู่ในโค้ดที่เราเขียนผิด
ไฟล์ตัวหลัง (08_softap_fallback) คือแผนสำรองของทั้งคอร์ส ถ้าวันไหนบอร์ดหาวงที่ตั้งไว้ไม่เจอ
เจอ TypeError: tuple indices เมื่อไร ให้นึกถึงบรรทัดนี้ทันที — เกือบทุกครั้งคือเรื่องเดียวกัน