תכנות מיקרו-בקרים בשפת MicroPython · פרק 11 — ספריות: התקנה וכתיבה⌂📚🏠
פרק 11

ספריות: התקנה וכתיבה

Libraries: Installing and Writing
3 שעות לימוד

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

בפרק הזה:
  • נסביר את תפקידם של sys.path ושל התיקייה lib, ונפרש את ההודעה ImportError.
  • נתקין ספרייה בעזרת mip, mpremote ו-Thonny, ונבדוק אותה לפני השימוש: הרישיון, המקור, הגרסה והקוד.
  • נפריד בין הגדרת הרכיב ללוגיקה של התוכנית, ונעביר את האפיק דרך הבנאי.
  • נקמפל ספרייה לקובץ .mpy ונדע מתי כדאי לעשות זאת.
  • נכתוב בדיקת יחידה עם אובייקט מדומה, ונבנה את המחלקה Button עם טיפול בקפיצות מגע.
  • נבדוק קוד שכתבה בינה מלאכותית, בלי לקבל שום מידע שאין לו מקור.

11.1 איפה import מחפש (The Search Path)

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

כשאנחנו כותבים import convert, מיקרו-פייתון מחפש את convert.py או את convert.mpy ברשימה של תיקיות, לפי הסדר, ולוקח את הראשון שהוא מוצא. הרשימה הזאת היא sys.path. הפלט תלוי בסביבה, ולכן כאן רק מציגים את הפקודה, בלי להריץ אותה בבניית הספר:

מציגים את הרשימה ומוסיפים לה תיקייה. לא מורץ בבניית הספר.
import sys

print(sys.path)
sys.path.append('/my_libs')

לפי התיעוד, בדרך כלל יש ברשימה את הפריט הריק '', כלומר התיקייה הנוכחית, וגם .frozen למודולים שבנויים בתוך מערכת הלוח, ותיקייה שנגמרת ב-/lib. לכן שמים את הספריות בתיקייה lib ואת התוכנית בשורש. כך השורש נשאר נקי, והספרייה נשארת נפרדת מהתוכנית.

שם המודול, וקריאה לפונקציה שבו.
import convert

print(convert.__name__)
print(convert.to_percent(32768))
ESP32 + Pico W · MicroPython 1.29 · simulated board
convert
50

אם הקובץ אינו נמצא בכל הרשימה, זו ההודעה:

שגיאה שחוזרת הרבה: הקובץ אינו קיים בלוח, או שהשם שונה.
import convert_v2

print(convert_v2.to_percent(32768))
ESP32 + Pico W · MicroPython 1.29 · simulated board
ImportError: no module named 'convert_v2'
אזהרה: לא נותנים לתוכנית שלנו שם של ספרייה שבה אנחנו משתמשים. קובץ בשם max7219.py בשורש קודם לקובץ שב-lib, ולכן התוכנית מייבאת את עצמה או גרסה ישנה, ומופיעות הודעות מוזרות.

11.2 התקנת ספריות (Installing Libraries)

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

1. מעתיקים את הקובץ בעצמנו:

ספרייה שכתבנו או הורדנו מעתיקים ללוח. הכלי mpremote עובד מהמחשב דרך USB:

העתקת ספרייה ללוח. לא הורץ בבניית הספר.
mpremote mkdir :lib
mpremote cp max7219.py :lib/
mpremote ls :lib

גם ב-Thonny עושים את אותו דבר מחלון הקבצים: בוחרים את הקובץ ומעלים אותו לתיקייה lib שבלוח.

2. הכלי mip בלוח:

לוח שמחובר לרשת מוריד בעצמו. האינדקס ברירת המחדל הוא micropython-lib, ו-mip בוחר את התיקייה הראשונה ב-sys.path שנגמרת ב-/lib. אפשר לשנות זאת עם target, בתנאי שהתיקייה נמצאת ב-sys.path.

שלוש צורות התקנה מהלוח. לא מורץ בבניית הספר.
import time
import network
import mip
from secrets import WIFI_SSID, WIFI_PASS

wlan = network.WLAN(network.STA_IF)
wlan.active(True)
wlan.disconnect()
wlan.connect(WIFI_SSID, WIFI_PASS)
while not wlan.isconnected():
    time.sleep(0.5)

mip.install('umqtt.simple')
mip.install('umqtt.simple', mpy=False)
mip.install('github:org/repo/path/foo.py', version='v1.2')
פקודהמה היא עושה
mip.install('name')מהאינדקס, עם קובצי .mpy מוכנים.
mip.install('name', mpy=False)עם קובצי .py: אפשר לקרוא אותם.
mip.install('name', version='x.y')גרסה מסוימת.
mip.install('name', target='/x')תיקייה אחרת, בתנאי שהיא נמצאת ב-sys.path.
mip.install('github:org/repo/foo.py', version='tag')קובץ ממאגר, בענף או בתג מסוים.

3. מהמחשב:

אותה פקודה מהמחשב, ללוח מחובר. לא הורץ בבניית הספר.
mpremote mip install umqtt.simple
mpremote mip install --target=/my_libs umqtt.simple
mpremote mip install github:org/repo@v1.2

ב-Thonny יש מנהל חבילות בתפריט Tools: מחפשים בו את השם ומתקינים. השמות ברשימות עשויים להשתנות מגרסה לגרסה. אחרי ההתקנה תמיד מוודאים: מסתכלים בתיקייה lib ומנסים import ישירות.

הערה: כל זה נוגע לשני לוחות המיקרו-פייתון. ב-Raspberry Pi 4/5, שהוא מחשב, ספריות מתקינים בעזרת pip בסביבה וירטואלית, כפי שראינו בפרק 6.

11.3 בודקים לפני שמשתמשים (Vetting a Library)

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

השאלהאיפה מוצאים את התשובהסימן אזהרה
הרישיון: האם מותר לי?הקובץ LICENSE וראש כל קובץאין רישיון בכלל
המקור: מי כתב אותה?המאגר הרשמי ובעליוקובץ שהועתק מאתר בלי שם
הגרסה: האם היא חיה?תאריך השינויים, התגים והתקלות הפתוחותלא נוסתה על הלוח שלנו
הקוד: מה הוא עושה?קוראים אותו: התקנה עם mpy=False נותנת את המקוררשת או קבצים שאין להם קשר לעבודתה

הרישיון אינו פרט שולי. הוא אומר מה מותר לנו:

הרישיוןמה הוא אומר בקצרה
MIT, BSDשימוש, שינוי ומכירה, בתנאי שמשאירים את נוסח הרישיון ואת שם הכותב.
Apache 2.0כמוהם, עם תנאים נוספים על שינויים ועל פטנטים.
GPLאם מפיצים תוכנה שנבנתה עליו, צריך להפיץ אותה באותו רישיון יחד עם הקוד שלה.
CC BY-NC-SAאין שימוש מסחרי, והיצירה הנגזרת תהיה באותו רישיון.
אין רישיוןאין רשות: הזכויות שמורות לבעליהן גם אם הקובץ חשוף באינטרנט.

קוראים את הקוד עם שאלות מוגדרות: מה הוא מייבא? האם הוא קובע בתוכו מספרי הדקים? האם הוא משתמש במודולים שמיוחדים ללוח אחר? האם הוא חוסם את התוכנית זמן רב ב-sleep? האם הוא מתחבר לכתובת ברשת? האם הוא מדפיס סיסמה או מבקש אותה?

פעילות: הפעילו את ארבע השאלות על הספרייה micropython-mfrc522 מהפרק הקודם. הרישיון הוא MIT והמקור ידוע. אבל התיאור שלה מזכיר רק ESP8266 ו-WiPy. מה זה אומר לנו? ומה בודקים בכל גרסה מותאמת?

11.4 כותבים ספרייה: הפרדה בין שכבות (Writing a Library: Separation)

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

עובדת, אבל אי אפשר להעביר אותה ולא לבדוק אותה.
from machine import Pin, SPI


class Matrix:
    def __init__(self):
        self.spi = SPI(2, 1000000, sck=Pin(18), mosi=Pin(23))
        self.cs = Pin(5, Pin.OUT, value=1)

    def bar(self, percent):
        lit = (percent * 8 + 50) // 100
        print('lit rows:', lit)
        for i in range(8):
            value = 0xFF if 7 - i < lit else 0
            self.cs.value(0)
            self.spi.write(bytes([i + 1, value]))
            self.cs.value(1)

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

שכבת הדרייבר: ספרייה שמכירה רכיב אחד: את האוגרים שלו, את הבייטים שלו ואת סדר ההפעלה שלו. היא לא יודעת מה מציירים, ועל איזה לוח עובדים.
שכבת הלוגיקה: חישוב טהור: נכנסים מספרים ויוצאים מספרים. היא לא מייבאת machine, ולכן בודקים אותה בכל מקום.
הזרקת האפיק דרך הבנאי: המחלקה לא יוצרת את האפיק, אלא מקבלת אותו מבחוץ כפרמטר: MAX7219(spi, cs). כך עבד ADS1115(i2c) בפרק 6, וכך עובד כל דרייבר בספר.
bar_main.pyprogram: connects everythingledbar.pylogic: no hardwaremax7219.pydriver: bytes and registersmachine.SPIthe real busFakeSPIin the unit test
התוכנית מחברת את השכבות, והדרייבר אינו יודע מי האפיק: אמיתי, או מדומה בבדיקה.
הקובץ ledbar.py, שכבת הלוגיקה: אין בו import
# ledbar.py - turns a percentage into rows for an 8x8 matrix. No hardware.
# (c) 2026 Fahed Ghanem - MIT License - fahed-lab.com


def bar_rows(percent):
    if not 0 <= percent <= 100:
        raise ValueError('percent is 0 to 100')
    lit = (percent * 8 + 50) // 100
    rows = bytearray(8)
    for i in range(lit):
        rows[7 - i] = 0xFF
    return rows

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

שלוש ספריות ותוכנית קצרה, ואין בהן מספר הדק.
import time
import board
from machine import Pin
from convert import to_percent
from ledbar import bar_rows
from max7219 import MAX7219

pot = board.analog_in(board.POT)
spi = board.spi(1000000)
cs = Pin(board.CS, Pin.OUT, value=1)
matrix = MAX7219(spi, cs)

while True:
    percent = to_percent(pot.read_u16())
    rows = bar_rows(percent)
    matrix.show(rows)
    print(percent, 'percent:', sum(1 for r in rows if r), 'rows lit')
    time.sleep(1)
ESP32 + Pico W · MicroPython 1.29 · simulated board
0 percent: 0 rows lit
25 percent: 2 rows lit
50 percent: 4 rows lit
100 percent: 8 rows lit
...

כללים למי שכותב ספרייה. את רובם רואים ב-ads1115.py וב-max7219.py:

שאלה 11.1: למה אין from machine import Pin ב-max7219.py?

11.5 קובצי .mpy (Precompiled Files)

בייבוא של .py הבקר מתרגם את הטקסט עצמו לקוד ביניים, וצריך לשם כך זיכרון. הכלי mpy-cross עושה זאת מראש במחשב, ונותן קובץ .mpy שמייבאים באותו שם:

קומפילציה במחשב, ואחר כך העתקה. לא הורץ בבניית הספר.
pip install mpy-cross
mpy-cross max7219.py
mpremote cp max7219.mpy :lib/
מתי משתמשיםמתי לא
ספרייה גדולה שהייבוא שלה נכשל ב-MemoryErrorכשאנחנו עדיין משנים את הספרייה כל דקה
ספרייה יציבה שמפיצים ללוחות רבים, בגרסה ידועהכשרוצים לקרוא את הקוד בלוח
שימו לב: גרסת קובץ ה-.mpy חייבת להתאים למה שמיקרו-פייתון בלוח תומך בו. אחרת רואים ValueError: incompatible .mpy file. את המספר בלוח מוצאים בשדה שלהלן, ומשווים למה ש-mpy-cross --version אומר. קובץ .mpy אינו דרך להסתיר סוד: הסיסמה נשארת בתוכו קריאה.
הבייט הנמוך הוא גרסת קובצי .mpy. לא מורץ בבניית הספר.
import sys

print(sys.implementation._mpy & 0xFF)

11.6 בדיקת יחידה עם אובייקט מדומה (Unit Tests with a Fake)

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

את שכבת הלוגיקה בודקים ישירות בשורות assert. זו הבדיקה כולה:

אין לוח ואין אפיק.
from ledbar import bar_rows

assert bar_rows(0) == bytes(8)
assert bar_rows(100) == b'\xff' * 8
assert bar_rows(50) == bytes(4) + b'\xff' * 4
print('ledbar: all tests passed')
ESP32 + Pico W · MicroPython 1.29 · simulated board
ledbar: all tests passed

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

הקובץ fakes.py: המדומים, שאינם עובדים עם שום חומרה
# fakes.py - stand-ins for the hardware, for unit tests. Runs anywhere.
# (c) 2026 Fahed Ghanem - MIT License - fahed-lab.com


class FakeSPI:
    def __init__(self):
        self.sent = []

    def write(self, data):
        self.sent.append(bytes(data))


class FakePin:
    def __init__(self, level=1):
        self.level = level
        self.lows = 0

    def value(self, x=None):
        if x is None:
            return self.level
        self.level = x
        if x == 0:
            self.lows += 1


class FakeClock:
    def __init__(self):
        self.t = 0

    def __call__(self):
        return self.t
בודקים את סדר ההפעלה, את חלון CS, את השורות ואת הגבול העליון של עוצמת ההארה.
from max7219 import MAX7219
from fakes import FakeSPI, FakePin

spi = FakeSPI()
cs = FakePin()
m = MAX7219(spi, cs)

assert spi.sent[0] == bytes([0x0F, 0]), 'test mode must go off first'
assert spi.sent[-1] == bytes([0x0C, 1]), 'turn on must be last'
assert cs.lows == len(spi.sent), 'one CS window for each packet'
assert cs.level == 1, 'CS must stay high'

spi.sent = []
m.show((1, 2, 3, 4, 5, 6, 7, 8))
assert len(spi.sent) == 8
assert spi.sent[7] == bytes([0x08, 8])

try:
    m.intensity(16)
    assert False, 'no error for intensity 16'
except ValueError:
    pass

print('max7219: all tests passed')
ESP32 + Pico W · MicroPython 1.29 · simulated board
max7219: all tests passed

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

הבדיקה נעצרת עם המשפט שכתבנו.
from max7219 import MAX7219
from fakes import FakeSPI, FakePin


class Swapped(MAX7219):
    def _send(self, reg, value):
        self.spi.write(bytes([value, reg]))


spi = FakeSPI()
m = Swapped(spi, FakePin())
assert spi.sent[0] == bytes([0x0F, 0]), 'test mode must go off first'
print('never printed')
ESP32 + Pico W · MicroPython 1.29 · simulated board
AssertionError: test mode must go off first
מגבלות הבדיקה: הבדיקה אומרת שהקוד עושה את מה שהנחנו, ואינה אומרת שההנחה שלנו נכונה. אם טעינו במספר אוגר, טעינו בו גם בבדיקה, והיא תצליח. את המספרים משווים לגיליון הנתונים, והרכיב האמיתי נשאר השופט האחרון.
הערה: הפקודה assert מספיקה לספרייה קטנה. ב-micropython-lib יש חבילה unittest למי שרוצה יותר, ולא השתמשנו בה בספר.

11.7 פרויקט: המחלקה Button (Project: A Button Class)

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

pinstatemspressed() = True
הקו העליון הוא מה שההדק קורא. הקו התחתון הוא המצב שאנחנו מאמינים לו, אחרי יציבות שאורכה ms.

נכתוב את המחלקה לפי מה שלמדנו: ההדק מגיע מבחוץ, וגם השעון, כדי שבבדיקה נוכל להזיז את הזמן בידיים:

הקובץ button.py, בשני לוחות המיקרו-פייתון
# button.py - a push button with debouncing, without blocking.
# pin: an input Pin, or any object with value(). clock: returns ms.
# (c) 2026 Fahed Ghanem - MIT License - fahed-lab.com
import time


class Button:
    def __init__(self, pin, ms=30, active=0, clock=time.ticks_ms):
        self.pin = pin
        self.ms = ms
        self.active = active
        self.clock = clock
        self.state = pin.value()
        self.last = self.state
        self.since = clock()

    def pressed(self):
        now = self.clock()
        level = self.pin.value()
        if level != self.last:
            self.last = level
            self.since = now
            return False
        if level == self.state:
            return False
        if time.ticks_diff(now, self.since) < self.ms:
            return False
        self.state = level
        return level == self.active

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

כל שורה היא רשימה שמתארת מה ההדק קורא, בצעדים של 5 אלפיות שנייה.
from button import Button
from fakes import FakePin, FakeClock


def count_presses(levels):
    pin = FakePin()
    clock = FakeClock()
    btn = Button(pin, ms=30, clock=clock)
    presses = 0
    for level in levels:
        pin.level = level
        clock.t += 5
        if btn.pressed():
            presses += 1
    return presses


STEADY = [0] * 12
BOUNCY = [0, 1, 0, 1, 0, 1] + [0] * 10
SPIKE = [0, 0, 1, 1, 1, 1]
RELEASE = [0] * 12 + [1, 0, 1, 0] + [1] * 12

assert count_presses(STEADY) == 1, 'a clean press'
assert count_presses(BOUNCY) == 1, 'bounces are one press'
assert count_presses(SPIKE) == 0, 'too short to be a press'
assert count_presses(RELEASE) == 1, 'release is not a press'
print('button: all tests passed')
ESP32 + Pico W · MicroPython 1.29 · simulated board
button: all tests passed

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

שתי לחיצות אמיתיות, ומספר הקצוות גדול יותר. המחלקה אינה מושפעת.
import time
import board
from machine import Pin
from button import Button

pin = Pin(board.BTN, Pin.IN, Pin.PULL_UP)
led = Pin(board.LED_EXT, Pin.OUT)
btn = Button(pin)

presses = 0
edges = 0
last = pin.value()

while True:
    level = pin.value()
    if last == 1 and level == 0:
        edges += 1
    last = level
    if btn.pressed():
        presses += 1
        led.value(presses % 2)
        print('presses', presses, ' falling edges', edges)
    time.sleep(0.005)
ESP32 + Pico W · MicroPython 1.29 · simulated board
presses 1  falling edges 3
presses 2  falling edges 5
...
שאלה 11.2: למה לא משתמשים ב-time.sleep(0.03) אחרי כל קריאה של ההדק, שהוא הפשוט ביותר?

11.8 קוד שכתב כלי AI (Reviewing AI-Written Code)

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

שגיאה שמופיעה מיד בהרצה.
from machine import Timer

tick = Timer(-1)
tick.init(period=1000, callback=lambda t: print('tick'))
ESP32 · MicroPython 1.29 · simulated board
ValueError: invalid Timer number

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

לכאורה הכול הצליח: התוכנית הדפיסה את המשפט שלה, והבייטים יצאו. אבל העוצמה לא השתנתה, כי האוגר שלה הוא 0x0A.
import board
from machine import Pin

BRIGHTNESS_REG = 0x0D

spi = board.spi(1000000)
cs = Pin(board.CS, Pin.OUT, value=1)
cs.value(0)
spi.write(bytes([BRIGHTNESS_REG, 8]))
cs.value(1)
print('brightness set to 8')
ESP32 + Pico W · MicroPython 1.29 · simulated board
brightness set to 8
[spi]
0d 08

לכן נוהגים בסדר קבוע:

מה בודקיםאיך
האם היא מתקמפלת ועובדת?מריצים אותה. זה המינימום, ואינו הוכחה לנכונות.
האם הפקודות קיימות?כל פונקציה לא מוכרת מחפשים ב-docs.micropython.org, ללוח הנכון.
האם המספרים נכונים?כל כתובת, אוגר וקביעת זמן משווים לגיליון הנתונים של הרכיב.
האם ההדקים נכונים?משווים לקובץ הלוח: הדק קלט בלבד, הדק שמור, או התנגשות כמו בפרק 10.
האם המתחים בטוחים?המתחים מגיליון הנתונים, לפני שמחברים חוט כלשהו.
איפה הסודות?אין סיסמה בקוד ואין בשיחה עם הכלי.
מאין הגיעה?ספרייה שהכלי מציע בודקים כמו ב-11.3, ומוודאים שהיא קיימת בכלל.
האם אנחנו מבינים אותה?מסוגלים להסביר כל שורה, וכותבים לה בדיקה בעצמנו.
כלל: לא מקבלים מספר רק כי הוא הופיע בתשובה מסודרת. אם הכלי אומר שזמן ההמרה הוא כך וכך או שהמתח המותר הוא כך וכך, זו טענה עד שמוצאים אותה במקור. אם לא מוצאים, כותבים: "לא מאומת", ולא שמים במקומה מספר שנראה טוב. מדידה אינה ממציאים: מה שלא מדדנו לא כותבים כתוצאה בדוח.

עיקרי הפרק:

פקודות הפרק:

פקודהמה היא עושה
sys.pathרשימת התיקיות ש-import מחפש בהן.
mip.install('name')התקנה מהלוח.
mpremote mip install nameהתקנה מהמחשב.
mpremote cp f.py :lib/העתקת קובץ ללוח.
mpy-cross f.pyקומפילציה ל-.mpy.
assert cond, 'text'עוצר את התוכנית אם התנאי שגוי.
raise ValueError('text')דיווח לתוכנית על ערך שגוי.
Button(pin, ms=30)מתג עם טיפול בקפיצות מגע; pressed().

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

עבריתEnglishערבית
ספרייהLibraryمكتبة
חבילהPackageحزمة
רישיוןLicenseرخصة
גרסהVersionإصدار
דרייברDriverطبقة التعريف
לוגיקהLogic Layerطبقة المنطق
הזרקה דרך הבנאיConstructor Injectionتمرير عبر الباني
בנאיConstructorالباني
קוד בינייםBytecodeشيفرة وسيطة
בדיקת יחידהUnit Testاختبار الوحدة
אובייקט מדומהFake Objectكائن بديل
קפיצות מגעContact Bounceارتداد التلامس
התשובות הסופיות לשאלות הפרק
11.1 כי ההדק מגיע מוכן מן התוכנית. מספיק שיש לו value. כך המחלקה עובדת גם עם אובייקט מדומה בבדיקה.
11.2 כי הוא חוסם את התוכנית: בזמן הזה אי אפשר לקרוא חיישן או לעדכן מסך. pressed() חוזרת מיד בכל פעם.

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

רמה ראשונה — בסיס
1.
איפה שמים את הספרייה בלוח? ולמה?
תשובות: בתיקייה lib, כי היא נמצאת ב-sys.path, וכי השורש נשאר לתוכנית.
2.
מה ההבדל בין mip.install('x') לבין mip.install('x', mpy=False)?
תשובות: הראשון מתקין קובצי .mpy, והשני את קובצי המקור .py שאפשר לקרוא.
3.
מצאנו קובץ באינטרנט בלי רישיון. האם משתמשים בו בפרויקט שמפיצים?
תשובות: אין לנו רשות. מבקשים רשות מבעליו, או מחפשים ספרייה עם רישיון, או כותבים בעצמנו מגיליון הנתונים.
רמה שנייה — יישום
4.
כתבו בדיקה ל-ADS1115 עם אפיק I2C מדומה: היא מוודאת את הערך השלילי ואת בחירת ערוץ 2.
תשובות:
from ads1115 import ADS1115


class FakeI2C:
    def __init__(self, value):
        self.value = value
        self.reg = 0
        self.config = 0

    def writeto(self, addr, data):
        self.reg = data[0]
        if len(data) == 3:
            self.config = (data[1] << 8) | data[2]

    def readfrom(self, addr, n):
        v = 0x8000 if self.reg == 1 else self.value
        return bytes([v >> 8, v & 0xFF])


bus = FakeI2C(0xFFFF)
adc = ADS1115(bus)
assert adc.read_raw(2) == -1, 'negative values'
assert (bus.config >> 12) & 7 == 6, 'channel 2'
print('ads1115: all tests passed')
מודפס: ads1115: all tests passed
5.
הוסיפו לבדיקה של Button מקרה של שתי לחיצות נקיות ברצף. מה מצפים?
תשובות: למשל: [0] * 12 + [1] * 12 + [0] * 12. מצפים ל-2.
6.
סווגו לדרייבר או ללוגיקה:
(א) המרת הקריאה הגולמית לטמפרטורה.
(ב) שליחת פקודת ההפעלה לרכיב.
(ג) קביעה אם המכשיר צריך קירור.
תשובות: (א) ו-(ג) לוגיקה. (ב) דרייבר.
רמה שלישית — העמקה
7.
איך מוסיפים ל-Button זיהוי לחיצה ארוכה, כך שבודקים אותו עם FakeClock?
תשובות: שומרים את זמן תחילת הלחיצה כשמאמינים למצב החדש, ומוסיפים פונקציה שמשווה את ticks_diff למשך הנדרש ומחזירה True פעם אחת. בבדיקה מגדילים את clock.t בידיים ולא מחכים.
8.
כלי בינה מלאכותית הציע לנו את הפקודה spi.write_read(tx). איך בודקים?
תשובות: מחפשים בתיעוד של machine.SPI: הפקודות המתועדות הן write, read, readinto ו-write_readinto, והפקודה הזאת אינה ביניהן.
9.
למה הבדיקה שלנו ב-11.6 אינה מגלה טעות במספר אוגר שכתבנו בטעות גם בספרייה וגם בבדיקה? ומה כן מגלה אותה?
תשובות: כי הבדיקה משווה את הקוד להנחה של כותבו. מגלים אותה בהשוואה לגיליון הנתונים ובניסוי ברכיב אמיתי.