תכנות מיקרו-בקרים בשפת MicroPython · פרק 14 — רשת WiFi ושרת HTTP⌂📚🏠
פרק 14

רשת WiFi ושרת HTTP

WiFi Network and HTTP Server
4 שעות לימוד

עד עכשיו הלוח דיבר איתנו דרך כבל. בפרק הזה הוא נכנס לאותה רשת אלחוטית שהטלפון שלנו מחובר אליה, ואנחנו פותחים בדפדפן דף שנמצא בתוך הלוח: קוראים ממנו חיישן ושולטים בנורית בלי שום כבל. נבנה את כל זה מכלים קטנים שנבין כל שורה בהם, ונכיר גם את גבולותיהם, כי הדף הזה פתוח לכל מי שנמצא ברשת.

בפרק הזה:
  • נסביר מהן כתובת IP ופורט, ומה ההבדל בין לקוח לשרת.
  • נחבר את הלוח לרשת עם פסק זמן, נטפל בכשל בחיבור, ונשאיר את הסיסמה מחוץ לתוכנית.
  • נפעיל את הלוח כתחנה ברשת קיימת וכנקודת גישה שיוצרת רשת משלה.
  • נכתוב שרת HTTP בעזרת socket: נקרא את הבקשה והנתיב, ונבנה את התגובה.
  • נשלוט בנורית מתוך דף, נציג קריאה, ונקבל ערך מטופס ונוודא אותו.
  • נקבע מה מותר לחבר לשרת שאין בו הצפנה ואין בו אימות.

14.1 כתובת IP, פורט ומודל לקוח-שרת (IP, Port, Client-Server)

לפני שורת הקוד הראשונה אנחנו צריכים ארבע מילים. לכל מכשיר ברשת יש כתובת, ובכל מכשיר רצות כמה תוכניות, ולכן כדי להגיע לתוכנית מסוימת צריך גם כתובת וגם מספר. בכל שיחה יש צד שמבקש וצד שעונה.

כתובת IP: ארבעה מספרים בין 0 ל-255 שמזהים את המכשיר ברשת, למשל 192.168.1.50. בדרך כלל הנתב מקצה אותה למכשיר כשהוא מצטרף לרשת.
פורט: מספר שמזהה את התוכנית בתוך המכשיר. שרתי דפים מאזינים בדרך כלל לפורט 80.
שרת: תוכנית שממתינה לבקשות ועונה להן. היא אף פעם לא פותחת את השיחה.
לקוח: תוכנית שפותחת את השיחה ושולחת בקשה, למשל הדפדפן.
הטלפון: הלקוח192.168.1.20הנתב192.168.1.1הלוח: השרת192.168.1.50 : 80בקשהתגובהבקשהתגובה
הטלפון מבקש, הנתב מעביר, והלוח עונה מפורט 80.

בפרק הזה הלוח הוא השרת. בפרק הבא נהפוך את התפקידים והלוח יהיה הלקוח.

דוגמה 14.1
מסכת הרשת בבתים רבים היא 255.255.255.0, כלומר שלושת המספרים הראשונים קבועים והמספר הרביעי הוא היחיד שמשתנה. כמה מכשירים יכולים לקבל כתובת ברשת הזאת, אם שתי כתובות שמורות, הראשונה והאחרונה?
פתרון:
N = 28 − 2
N = 256 − 2
N = 254
N = 254
שאלה 14.1: על אותו לוח רצים שני שרתים. האם שניהם יכולים להאזין לפורט 80? ואיך הדפדפן מגיע לשני?

14.2 הצטרפות לרשת (Joining a Network)

בחיבור האלחוטי ב-MicroPython משתמשים במחלקה network.WLAN, ולה שני מצבים: במצב תחנה הלוח מצטרף לרשת קיימת כמו הטלפון, ובמצב נקודת גישה הוא יוצר רשת משלו.

מצב תחנהמצב נקודת גישההטלפוןהנתבהלוח192.168.1.50הטלפוןהלוח192.168.4.1
במצב תחנה הדרך עוברת דרך הנתב, ובמצב נקודת גישה הטלפון מתחבר ישירות אל הלוח.

נתחיל בשאלה: מה הלוח רואה סביבו? כל תוצאה בסריקה כוללת את שם הרשת, את הערוץ ואת עוצמת האות. עוצמת האות rssi היא מספר שלילי, וככל שהוא קרוב יותר לאפס האות חזק יותר.

סריקת רשתות. השמות והמספרים הם מן העולם המדומה.
import network

wlan = network.WLAN(network.STA_IF)
wlan.active(True)
for ssid, bssid, channel, rssi, sec, hidden in wlan.scan():
    print(ssid.decode(), channel, rssi)
ESP32 + Pico W · MicroPython 1.29 · simulated board
Home 6 -50
Lab 11 -71
Cafe 1 -85
הערה: רשת 5 GHz אינה נראית ללוחות של הספר הזה: ESP32 ו-Pico W פועלים בתדר 2.4 GHz בלבד.

הפקודה connect אינה ממתינה: היא מתחילה את החיבור וחוזרת מיד. לכן אנחנו שואלים isconnected() בלולאה, וקובעים לה פסק זמן כדי שהתוכנית לא תיתקע לנצח אם הסיסמה שונתה או שהנתב כבה.

הצטרפות עם פסק זמן.
import network
import time
from secrets import WIFI_SSID, WIFI_PASS

WAIT_S = 4

wlan = network.WLAN(network.STA_IF)
wlan.active(True)
wlan.disconnect()
wlan.connect(WIFI_SSID, WIFI_PASS)

waited = 0
while not wlan.isconnected() and waited < WAIT_S:
    print("waiting", waited)
    time.sleep(1)
    waited += 1

if wlan.isconnected():
    print("IP:", wlan.ifconfig()[0])
else:
    print("failed, status", wlan.status())
    wlan.active(False)
ESP32 + Pico W · MicroPython 1.29 · simulated board
waiting 0
IP: 192.168.1.50

לפני connect קוראים ל-disconnect(): אם ניסיון קודם עדיין רץ מהפעלה קודמת, ESP32 נותן את השגיאה Wifi Internal Error. השגיאה הזאת הופיעה בפועל כשניסינו את התוכנית על לוח אמיתי.

וזו אותה תוכנית כשהרשת רחוקה מהישג יד:

ESP32 · MicroPython 1.29 · simulated board
waiting 0
waiting 1
waiting 2
waiting 3
failed, status 201

המספר ש-status() מחזירה מתפרש לפי קבוע מהמודול network, למשל STAT_WRONG_PASSWORD או STAT_NO_AP_FOUND. הערכים המספריים עשויים להיות שונים בין הלוחות, ולכן משווים תמיד לקבוע ולא למספר.

טעות נפוצה: לולאה while not wlan.isconnected(): pass בלי פסק זמן היא הסיבה המפורסמת ביותר ללוח "מת": אין הודעה ואין שגיאה, והתוכנית כולה עומדת על אותה שורה.
שאלה 14.2: למה אנחנו מכבים את הכרטיס בעזרת wlan.active(False) אחרי הכישלון, ולא משאירים אותו פעיל?

14.3 קובץ הסודות וספריית wifi.py

שימו לב שהסיסמה לא הופיעה באף תוכנית. היא נמצאת בקובץ אחד שאנחנו מעלים ללוח ולא מעלים לשום מקום אחר:

הקובץ secrets.py: מחליפים את הערכים בשם הרשת שלנו ובסיסמה שלה, ובסיסמה חדשה לנקודת הגישה
# secrets.py - your network. Keep it off GitHub and shared folders.
WIFI_SSID = "my-network"
WIFI_PASS = "my-password"
AP_PASS = "pick-a-new-one"
כלל קבוע: תוכניות מפיצים: שולחים אותן לחבר, מעלים אותן ל-GitHub, או מעתיקים אותן לדוח. סיסמה שכתובה בשורה בתוכנית נוסעת איתה, ונשארת בהיסטוריה של המאגר גם אחרי שמחקנו אותה. לכן לא כותבים בקוד משותף סיסמה או מפתח, ומוסיפים את secrets.py לקובץ .gitignore.

מכיוון שכל תוכנית בהמשך הספר מתחילה בהצטרפות לרשת, נעביר את הלולאה לספרייה. הספרייה לא מדפיסה דבר: אם החיבור נכשל היא מעלה שגיאה, והתוכנית שקוראת לה מחליטה מה לעשות.

הספרייה wifi.py, זהה בשני הלוחות
# wifi.py - join the network named in secrets.py, or say why not.
# (c) 2026 Fahed Ghanem - MIT License - fahed-lab.com
import network
import time
from secrets import WIFI_SSID, WIFI_PASS


def connect(timeout_s=15):
    wlan = network.WLAN(network.STA_IF)
    wlan.active(True)
    if wlan.isconnected():
        return wlan
    wlan.disconnect()
    wlan.connect(WIFI_SSID, WIFI_PASS)
    start = time.ticks_ms()
    while not wlan.isconnected():
        if time.ticks_diff(time.ticks_ms(), start) > timeout_s * 1000:
            status = wlan.status()
            wlan.active(False)
            raise OSError("WiFi failed, status %d" % status)
        time.sleep_ms(200)
    return wlan
דוגמה 14.2
הספרייה בודקת את החיבור כל 0.2 s, ופסק הזמן הוא 15 s. כמה בדיקות לכל היותר היא מבצעת לפני שתכריז על כישלון?
פתרון:
N = Tt
N = 150.2
N = 75
N = 75
שימוש בספרייה: כל הלולאה הפכה לקריאה אחת.
import wifi

try:
    wlan = wifi.connect(timeout_s=5)
    print("IP:", wlan.ifconfig()[0])
except OSError as e:
    print("no network:", e)
ESP32 + Pico W · MicroPython 1.29 · simulated board
IP: 192.168.1.50

וכשהחיבור נכשל, השגיאה מגיעה אל except:

ESP32 · MicroPython 1.29 · simulated board
no network: WiFi failed, status 201
שאלה 14.3: פתחנו את הספרייה ומצאנו את השורה if wlan.isconnected(): return wlan לפני connect. מה התועלת שלה כשמפעילים את התוכנית מחדש בלי לנתק את המתח?

14.4 הלוח כנקודת גישה (Access Point Mode)

בשטח או בסדנה לא תמיד יש נתב. אז הלוח יוצר רשת משלו: אנחנו בוחרים אותה מרשימת הרשתות בטלפון, ופותחים את הכתובת שהוא מקצה לעצמו.

רשת בשם lab-board. הכתובת כאן היא הכתובת שההדמיה מקצה.
import network
from secrets import AP_PASS

ap = network.WLAN(network.AP_IF)
ap.active(True)
ap.config(ssid="lab-board", key=AP_PASS)
ap.active(False)
ap.active(True)
print("network:", ap.config("ssid"))
print("IP:", ap.ifconfig()[0])
ESP32 + Pico W · MicroPython 1.29 · simulated board
network: lab-board
IP: 192.168.4.1

קודם מפעילים את הממשק, אחר כך מגדירים אותו, ואז מפעילים אותו מחדש, כי כך זה עובד גם בגרסה ישנה: ב-ESP32 עם קושחה ישנה, הגדרה לפני ההפעלה נותנת שגיאה. לא ניסינו את הסדר הזה ב-Pico W אמיתי.

אזהרה: לנקודת הגישה יש סיסמה משלה, ולא משתמשים בה בסיסמה של הרשת הביתית. נוודא בטלפון שהרשת החדשה אכן דורשת סיסמה. הדרך לקבוע את סוג האבטחה בעזרת הפרמטר security והקבועים שלו תלויה בלוח, ולכן נפנה לתיעוד של הלוח שבידינו. כדאי שסיסמת נקודת הגישה לא תהיה סיסמת הבית.

כל מה שיבוא בהמשך הפרק, מהשרת ועד הדפים, פועל בשני המצבים. השרת לא מתעניין איך הלקוח הגיע, אלא רק בכך שהגיע.

שאלה 14.4: במצב נקודת גישה, האם תוכנית בלוח יכולה לבקש דף מהאינטרנט? ולמה?

14.5 שרת HTTP ב-socket (An HTTP Server on a Socket)

שקע (socket): האובייקט שמייצג קצה של חיבור ברשת: כותבים אליו והכתיבה מגיעה לצד השני, וקוראים ממנו את מה שהצד השני שלח.
פרוטוקול HTTP: הסכם טקסטואלי בין לקוח לשרת: בקשה אחת שאחריה תגובה אחת.
הדפדפן: הלקוחהלוח: השרתGET /on HTTP/1.1HTTP/1.0 200 OK + HTMLclose()
שיחה שלמה: בקשה, תגובה, סגירה.

השורה הראשונה בבקשה אומרת הכול: השיטה GET, אחריה הנתיב /on, ואחריו הגרסה. גם התגובה היא טקסט בסדר קבוע: שורת המצב, אחריה הכותרות, אחריהן שורה ריקה, ואז הדף עצמו.

שרת שלם. הבקשה כאן מגיעה מדפדפן מדומה, ובסוף הפלט מופיע מה שהלוח שלח על השקע.
import socket
import wifi

wlan = wifi.connect()
print("open http://%s/" % wlan.ifconfig()[0])

addr = socket.getaddrinfo("0.0.0.0", 80)[0][-1]
server = socket.socket()
server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
server.bind(addr)
server.listen(2)

while True:
    conn, peer = server.accept()
    request = conn.recv(1024)
    print(peer[0], request.split(b"\r\n")[0].decode())
    conn.sendall(b"HTTP/1.0 200 OK\r\n"
                 b"Content-Type: text/html\r\n\r\n"
                 b"<h1>Hello from the board</h1>")
    conn.close()
ESP32 + Pico W · MicroPython 1.29 · simulated board
open http://192.168.1.50/
192.168.1.20 GET / HTTP/1.1
[http_sent]
HTTP/1.0 200 OK
Content-Type: text/html

<h1>Hello from the board</h1>
...
פקודהמה היא עושה
getaddrinfo("0.0.0.0", 80)כתובת שמשמעותה: האזן בכל הממשקים של הלוח, בפורט 80.
setsockopt(... SO_REUSEADDR, 1)מאפשר להפעיל את התוכנית מחדש מיד על אותו פורט.
server.listen(2)מתחיל להאזין, ומשאיר שני לקוחות בהמתנה.
server.accept()ממתין ללקוח, ומחזיר שקע ייחודי לו יחד עם הכתובת שלו.
conn.recv(1024)קורא עד 1024 בתים מן הבקשה.
conn.sendall(...)שולח את התגובה כולה.
conn.close()סוגר את החיבור, ואז הדפדפן יודע שהדף נגמר.
טעות נפוצה: השורה הריקה בין הכותרות לדף איננה קישוט: \r\n\r\n היא מה שמפריד ביניהם. בלעדיה כל הדף נקרא כאילו הוא כותרות, ולא מופיע דבר בדפדפן.
שאלה 14.5: שכחנו את conn.close() בלולאה. מה המשתמש רואה? ומה קורה ללוח אחרי כמה בקשות?

14.6 דף ששולט בנורית (A Page that Drives the LED)

נתחיל במה שחוזר על עצמו: פתיחת השקע, פירוק השורה הראשונה ובניית התגובה. נאגד את כל זה בספרייה קטנה, כדי שבתוכנית יישארו רק השורות שאומרות מה המכשיר עושה.

הספרייה miniweb.py
# miniweb.py - a tiny web server on plain sockets, one client at a time.
# (c) 2026 Fahed Ghanem - MIT License - fahed-lab.com
import socket


def listen(port=80):
    addr = socket.getaddrinfo("0.0.0.0", port)[0][-1]
    server = socket.socket()
    server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    server.bind(addr)
    server.listen(2)
    return server


def parse(raw):
    parts = raw.split(b"\r\n")[0].decode().split(" ")
    if len(parts) < 2:
        return "", {}
    target = parts[1].split("?", 1)
    args = {}
    if len(target) == 2:
        for pair in target[1].split("&"):
            kv = pair.split("=", 1)
            if len(kv) == 2:
                args[kv[0]] = kv[1]
    return target[0], args


def reply(conn, body, status="200 OK", kind="text/html"):
    head = "HTTP/1.0 %s\r\nContent-Type: %s\r\n\r\n" % (status, kind)
    conn.sendall(head.encode() + body.encode())
דוגמה 14.3
מה parse מחזירה עבור הבקשה GET /set?level=60&mode=slow HTTP/1.1?
פתרון:

חותכים את השורה ברווחים, והחלק השני הוא היעד. אחר כך חותכים אותו ב-?: לפניו הנתיב, ואחריו הפרמטרים, מופרדים ב-&. כל הערכים הם מחרוזות.

miniweb.parse
('/set', {'mode': 'slow', 'level': '60'})
כל קישור הוא בקשה חדשה. הסדר: הדף, הדלקה, בקשת האייקון, כיבוי.
from machine import Pin
import board
import miniweb
import wifi

PAGE = """<h1>LED: %s</h1>
<a href="/on">ON</a> <a href="/off">OFF</a>"""

led = Pin(board.LED, Pin.OUT)
wifi.connect()
server = miniweb.listen()

while True:
    conn, peer = server.accept()
    conn.settimeout(3)
    try:
        path, args = miniweb.parse(conn.recv(1024))
        print(peer[0], path)
        if path == "/on":
            led.value(1)
        elif path == "/off":
            led.value(0)
        if path in ("/", "/on", "/off"):
            state = "ON" if led.value() else "OFF"
            miniweb.reply(conn, PAGE % state)
        else:
            miniweb.reply(conn, "Not found", "404 Not Found")
    except OSError as e:
        print("client error:", e)
    finally:
        conn.close()
ESP32 · MicroPython 1.29 · simulated board
192.168.1.20 /
192.168.1.20 /on
192.168.1.20 /favicon.ico
192.168.1.20 /off
[pins]
2 1
2 0
...
Pico W · MicroPython 1.29 · simulated board
192.168.1.20 /
192.168.1.20 /on
192.168.1.20 /favicon.ico
192.168.1.20 /off
[pins]
100 1
100 0
...

הביטו בבקשה השלישית: לא לחצנו על שום דבר שמוביל ל-/favicon.ico. הדפדפנים מבקשים את הקובץ הזה מעצמם כדי להציג את אייקון האתר, ולכן כל שרת צריך להשיב 404 על מה שהוא לא מכיר. וגם גישה לנתיב לא מוכר לא משנה את מצב הנורית.

ולמה conn.settimeout(3)? כי השרת הזה משרת לקוח אחד בכל פעם. לקוח שפתח חיבור ולא שלח דבר עוצר את כל השרת ב-recv. פסק הזמן מעלה OSError, ו-finally סוגר את השקע בכל מקרה, והשרת חוזר ל-accept.

אזהרה: השרת כאן תופס את כל התוכנית: כל עוד accept ממתין, לא רצים לולאת מדידה או טיימר בתוכנית הראשית. את זה נפתור בפרק התכנות האסינכרוני.
שאלה 14.6: הדפדפן פותח שוב את הקישור האחרון כשמרעננים את הדף. מה קורה ברענון אחרי שלחצנו על /on? ולמה זה לא מזיק כאן, ומתי כן?

14.7 הצגת קריאה וטופס (Showing a Reading, Receiving a Value)

נעשה צעד אחרון: דף שמציג את המתח של הפוטנציומטר, ובו טופס ששולט בעוצמת התאורה של נורית חיצונית. הטופס ב-HTML לא צריך שום קוד מיוחד: כשלוחצים על הלחצן הדפדפן מבקש /set?level=60, כלומר נתיב ופרמטר, וזה בדיוק מה שאנחנו יודעים לקרוא.

10 kΩ3V334 · GP26 (POT)13 · GP15 (LED_EXT)GND330 Ω
החיווט: המספר הראשון ל-ESP32, והשני ל-Pico W. התוכנית אינה מזכירה אף אחד מהם בזכות board.py.
חמש בקשות: הדף, ערך תקין, טקסט, מספר מחוץ לתחום ובקשה בלי פרמטר.
import board
import miniweb
import wifi
from convert import to_volts

PAGE = """<h1>%.2f V</h1><p>LED: %d%%</p>
<form action="/set">
<input name="level" type="number" min="0" max="100">
<button>Set</button></form>"""

pot = board.analog_in(board.POT)
led = board.pwm(board.LED_EXT, 1000)
led.duty_u16(0)
level = 0
wifi.connect()
server = miniweb.listen()


def handle(path, args):
    global level
    if path == "/set":
        value = int(args["level"])
        if not 0 <= value <= 100:
            raise ValueError("range")
        level = value
        led.duty_u16(level * 65535 // 100)
    elif path != "/":
        return "Not found", "404 Not Found"
    return PAGE % (to_volts(pot.read_u16()), level), "200 OK"


while True:
    conn, peer = server.accept()
    conn.settimeout(3)
    try:
        path, args = miniweb.parse(conn.recv(1024))
        try:
            body, status = handle(path, args)
        except (KeyError, ValueError):
            body, status = "Bad value", "400 Bad Request"
        print(path, args, status, "level", level)
        miniweb.reply(conn, body, status)
    except OSError as e:
        print("client error:", e)
    finally:
        conn.close()
ESP32 · MicroPython 1.29 · simulated board
/ {} 200 OK level 0
/set {'level': '60'} 200 OK level 60
/set {'level': 'abc'} 400 Bad Request level 60
/set {'level': '150'} 400 Bad Request level 60
/set {} 400 Bad Request level 60
[pwm]
13 0
13 39321
...
Pico W · MicroPython 1.29 · simulated board
/ {} 200 OK level 0
/set {'level': '60'} 200 OK level 60
/set {'level': 'abc'} 400 Bad Request level 60
/set {'level': '150'} 400 Bad Request level 60
/set {} 400 Bad Request level 60
[pwm]
15 0
15 39321
...
דוגמה 14.4
בקשת הטופס level=60. איזה מחזור עבודה התוכנית מציבה ב-duty_u16?
פתרון:
D = level · 65535100
D = 60 · 65535100
D = 39321
D = 39321
כלל קבוע: התכונות min ו-max בטופס אינן מגנות על דבר: מספיק שמישהו יכתוב את הכתובת ישירות בדפדפן כדי לעקוף את הטופס. כל ערך שמגיע מהרשת בודקים בלוח עצמו, ולעולם לא סומכים עליו. לכן התוכנית משיבה 400 ולא משנה את המצב.
שאלה 14.7: אות עברי או רווח בערך מטופס מגיעים ללוח מקודדים, למשל a%20b עבור a b. האם הספרייה miniweb מפענחת את הקידוד הזה? ומה עושים אם צריך?

14.8 אבטחה: מה מותר לחבר (Security Limits)

השרת שכתבנו עובד, אבל עלינו לומר במפורש מה חסר בו:

מה מחברים לשרת הזהההחלטההסיבה
נורית, קריאת טמפרטורה או אור, תצוגה במעבדהמתאיםבמקרה הגרוע התוצאה מפריעה אך אינה מסוכנת.
מנוע קטן, זמזם, משאבה קטנה במתח נמוךבזהירותמגבילים את זמן ההפעלה בתוכנית, ולא משאירים אותם פועלים ללא השגחה.
מנעול דלת, תנור, כל עומס במתח הרשתלאכל אחד ברשת יכול להפעיל אותו, וחזרה על בקשה אחת עלולה לגרום לשריפה או לנזק.
פתיחת פורט בנתב אל האינטרנטלאהשרת נחשף לכל העולם בלי שום הגנה.

הכלל שלנו: השרת הזה מיועד לרשת מעבדה שאנחנו מכירים את כל מי שנמצא בה. לכל מפעיל שמחברים אליו בוחרים מצב שאינו מזיק כשהרשת נופלת, וזו החלטה של התוכנית ולא של הרשת.

שאלה 14.8: תלמיד רוצה לשלוט במאוורר 230 V מהטלפון שלו בעזרת התוכנית של הפרק הזה. מה אומרים לו?

14.9 הבדלים בין הלוחות ופריסה (Board Differences, Deployment)

נחזור למה שהבטחנו בפרק הקלט האנלוגי. ב-ESP32 בלוק הממירים השני משמש את ה-WiFi, ולכן קריאה מהדק שנמצא בו נכשלת כל עוד הכרטיס פעיל:

הדק 25 בבלוק השני, והשגיאה מופיעה רק אחרי הפעלת הכרטיס.
from machine import ADC, Pin
import network

wlan = network.WLAN(network.STA_IF)
wlan.active(True)
pot = ADC(Pin(25), atten=ADC.ATTN_11DB)
print(pot.read_u16())
ESP32 · MicroPython 1.29 · simulated board
OSError: ADC2 is in use by WiFi

בגלל זה board.POT ב-ESP32 הוא הדק 34, מן התחום 32–39. ב-Pico W אין מגבלה כזאת, והדקים GP26–GP28 פועלים עם הרשת.

כל התוכניות בפרק פעלו בשני הלוחות בלי לשנות שורה, וזה מה שהראו המסכים למעלה. העבודה היחידה היא להעלות ללוח את הקבצים שהתוכנית מייבאת, ואז לקרוא לתוכנית main.py כדי שתרוץ בהפעלה:

mpremote cp secrets.py wifi.py miniweb.py board.py convert.py :
mpremote cp http_panel.py :main.py
mpremote reset
הערה: ב-Raspberry Pi 4/5 אין network.WLAN: מערכת לינוקס היא שמצטרפת לרשת, ודי בכך שהתוכנית תפתח את השקע. לפאי יש פרק משלו בחלק השישי.

עיקרי הפרק:

פקודות הפרק:

פקודהמה היא עושה
network.WLAN(network.STA_IF)כרטיס במצב תחנה. AP_IF לנקודת גישה.
wlan.active(True)הפעלת הכרטיס.
wlan.scan()רשימת הרשתות סביב הלוח.
wlan.connect(ssid, key)תחילת ההצטרפות, בלי המתנה.
wlan.isconnected()האם ההצטרפות הושלמה?
wlan.status()מצב החיבור, משווים לקבועים network.STAT_*.
wlan.ifconfig()[0]כתובת הלוח.
ap.config(ssid=..., key=...)שם נקודת הגישה והסיסמה שלה.
wifi.connect(timeout_s)הספרייה שלנו: הצטרפות עם פסק זמן, או OSError.
miniweb.listen(port)הספרייה שלנו: שקע שרת מוכן.
miniweb.parse(raw)הספרייה שלנו: הנתיב והפרמטרים מתוך הבקשה.
miniweb.reply(conn, body, status)הספרייה שלנו: שליחת תגובה.

מילון מונחים:

עבריתEnglishערבית
כתובת IPIP Addressعنوان IP
פורטPortالمنفذ
נתבRouterالموجّه
שרתServerالخادم
לקוחClientالعميل
דפדפןBrowserالمتصفّح
תחנהStationالمحطة
נקודת גישהAccess Pointنقطة الوصول
פסק זמןTimeoutالمهلة
שקעSocketالمقبس
בקשהRequestالطلب
תגובהResponseالاستجابة
נתיבPathالمسار
כותרתHeaderالترويسة
טופסFormالنموذج
הצפנהEncryptionالتشفير
אימותAuthenticationالمصادقة
התשובות הסופיות לשאלות הפרק
14.1 לא: לכל פורט יש תוכנית אחת. את השרת השני מפעילים על פורט אחר, למשל 8080, וכותבים בדפדפן http://192.168.1.50:8080/.
14.2 כדי שהוא לא ימשיך לצרוך אנרגיה ולנסות ברקע בלי תועלת, והניסיון הבא יתחיל ממצב נקי.
14.3 ייתכן שהכרטיס עדיין מחובר מההפעלה הקודמת, ואז אנחנו משתמשים בחיבור הקיים במקום לנתק אותו ולהתחיל מחדש.
14.4 לא: הרשת שהלוח יצר אינה מחוברת לנתב, ולכן אין ממנה דרך לאינטרנט.
14.5 הדפדפן ממשיך לחכות לסוף הדף. והשקעים נשארים פתוחים בזיכרון עד שהם נגמרים, ואז הלוח מפסיק לקבל לקוחות חדשים.
14.6 הבקשה /on נשלחת שוב. זה לא מזיק כי התוצאה זהה בכל פעם. זה מזיק כשהבקשה משנה משהו בכל חזרה, למשל /toggle או הפעלת משאבה למשך זמן קבוע.
14.7 היא אינה מפענחת: הערך מגיע כמות שהוא. כותבים פונקציה קטנה שהופכת כל %XX לבית, או מסתפקים בערכים מספריים כמו כאן.
14.8 לא עושים את זה: אין אימות ואין הצפנה, והעומס במתח מסוכן. נשארים במתח נמוך במעבדה, ומנתקים עומס מסוכן מכל שליטה לא מוגנת.

שאלות לתרגול:

רמה ראשונה — בסיסי
1.
במצב תחנה ובמצב נקודת גישה: איזה משני המצבים צריך נתב? ואיזה מחבר את הלוח לאינטרנט?
תשובות: מצב תחנה בשני המקרים, אם הנתב מחובר לאינטרנט.
2.
סדרו את חלקי תגובת HTTP: הדף, שורה ריקה, שורת המצב, הכותרות.
תשובות: שורת המצב, הכותרות, שורה ריקה, הדף.
3.
מה parse מחזירה עבור הבקשה GET /on HTTP/1.1?
תשובות: את הנתיב /on ומילון פרמטרים ריק.
רמה שנייה — יישום
4.
הוסיפו לתוכנית הנורית נתיב /toggle שהופך את מצבה. האם די בשורה הזאת לבדה?
תשובות:
if path == "/toggle":
    led.value(1 - led.value())
לא: צריך להוסיף את הנתיב גם לרשימת הנתיבים המוכרים, אחרת התגובה תהיה 404 אף שהנורית השתנתה.
5.
בקשת הטופס level=25. מה הערך של duty_u16?
תשובות: D = 16383.
6.
הוסיפו לתוכנית הלוח נתיב /api שמחזיר את הקריאה בפורמט JSON במקום דף.
תשובות:
import json

if path == "/api":
    volts = to_volts(pot.read_u16())
    text = json.dumps({"volts": volts, "level": level})
    miniweb.reply(conn, text, "200 OK", "application/json")
רמה שלישית — העמקה
7.
הוסיפו לתוכנית ההצטרפות ניסיון שני אחרי כישלון, כך שהזמן הכולל לא יעלה על 30 s. איך כותבים את זה בעזרת wifi.py?
תשובות: קוראים ל-wifi.connect(15) בתוך try פעמיים בלולאת for, ויוצאים בהצלחה הראשונה. אחרי הפעם השנייה נותנים לשגיאה להגיע לתוכנית, או עוברים למצב נקודת גישה.
8.
חבר פתח חיבור לפורט 80 ולא שלח דבר, ואין בתוכנית settimeout. מה קורה לשאר המשתמשים? ולמה זה לא קורה בתוכנית שלנו?
תשובות: השרת נשאר ב-recv ואינו משרת אף אחד. בתוכנית שלנו פסק הזמן מסתיים אחרי 3 שניות, finally סוגר את השקע, והשרת חוזר ל-accept.
9.
תכננו שרת למעבדה שיש בה עשרה תלמידים, ורק תלמיד אחד שולט בו בנורית. האם מספיקה מילה בקישור, כמו /on?pw=1234?
תשובות: היא מונעת רק טעות בלתי מכוונת ואינה מגינה: המילה עוברת כטקסט גלוי, ונשארת בהיסטוריה של הדפדפן. הפתרון הפשוט ביותר הוא רשת נקודת גישה ייעודית לתלמיד הזה.