Beetje Coderen

Les 2 · Module 1 · De ESP32 als sensorknooppunt

Projectstructuur: boot.py, main.py en modules

Deel de code van het meetstation op in modules met een eigen lib-map, een config-bestand en een veilig opstartpad, en leer hoe imports, geheugen en bytecode op een microcontroller werken.

⏱ 60 minuten leestijd + oefenen

Illustratie: documenten en dozen naast een printplaatje, een opgeruimd project

🎯 Na deze les kun je

  • een MicroPython-project opdelen in modules en een lib-map
  • een veilig opstartpad maken zodat je nooit buitengesloten raakt
  • bestanden synchroniseren met mpremote en een klein script
  • geheugen besparen met mpy-cross en const()

Een meetstation wordt al snel honderden regels: sensoren, scherm, wifi, MQTT, configuratie. Alles in main.py is onoverzichtelijk en lastig te testen. In deze les zet je de structuur neer die je de rest van de cursus gebruikt.

De mappen

Op je computer:

meetstation/
  boot.py
  main.py
  config.py           # instellingen (les 17 haalt geheimen hieruit)
  lib/
    sensor.py         # les 3
    filter.py         # les 4
    scherm.py         # les 5
    net.py            # les 8
  tools/
    upload.sh

MicroPython zoekt modules in / en /lib (zie import sys; print(sys.path)). Bibliotheken van anderen (bijv. ssd1306.py) horen ook in lib/.

main.py als dirigent

# main.py
import time
import config
from lib import sensor   # of: import sensor (lib staat in sys.path)

def start():
    print("meetstation", config.NAAM, "start")
    while True:
        print("meting...")
        time.sleep(config.INTERVAL_S)

try:
    start()
except KeyboardInterrupt:
    print("gestopt")
# config.py
NAAM = "woonkamer"
INTERVAL_S = 10

Een veilig opstartpad

Voorspel eerst: je zet een main.py met een fout die meteen machine.deepsleep() aanroept, of een lus die de USB-poort volspamt. Hoe kom je nog bij je bordje?

Uitkomst

Lastig: het bordje herstart, draait main.py meteen opnieuw, en mpremote krijgt geen tijd om te verbinden. Vaak lukt het nog door direct na het aansluiten heel snel Ctrl+C te sturen, en anders moet je opnieuw flashen (en ben je je bestanden kwijt). Voorkom het met een ontsnappingsluik bovenaan main.py: als een knop bij het opstarten ingedrukt is, stopt main.py meteen en kom je in de REPL.

# main.py (bovenaan)
from machine import Pin
import time

BOOTKNOP = Pin(0, Pin.IN, Pin.PULL_UP)   # BOOT-knop op de meeste DevKits
time.sleep_ms(300)
if BOOTKNOP.value() == 0:
    print("veilige modus: programma niet gestart")
    raise SystemExit

Een exception die main.py beëindigt, brengt je naar de REPL. Houd de BOOT-knop ingedrukt, druk kort op EN (reset) en laat BOOT na een seconde los: je komt in de veilige modus. Houd boot.py zelf zo klein mogelijk; een fout dáár is lastiger te herstellen. Daarnaast is een paar seconden wachten in main.py vóór een deep sleep een goede gewoonte (les 15).

Uploaden

#!/usr/bin/env bash
# tools/upload.sh
set -e
mpremote mkdir :lib 2>/dev/null || true
for f in boot.py main.py config.py; do mpremote cp "$f" ":$f"; done
for f in lib/*.py; do mpremote cp "$f" ":$f"; done
mpremote reset

Tijdens ontwikkelen: mpremote mount . run main.py gebruikt je bestanden direct vanaf je computer, zonder kopiëren.

Geheugen en bytecode

Bij een import compileert MicroPython de broncode naar bytecode, in het RAM. Grote modules kunnen daardoor een MemoryError geven bij het importeren. Twee hulpmiddelen:

pip install mpy-cross
mpy-cross lib/sensor.py        # maakt lib/sensor.mpy: voorgecompileerd, kleiner, sneller te laden

Kopieer dan sensor.mpy in plaats van sensor.py (de mpy-cross-versie moet bij je firmware passen). En voor constanten:

from micropython import const
ADRES = const(0x76)    # wordt bij compileren ingevuld; kost geen geheugen voor een variabele

Typische fouten

  • ImportError: no module named 'sensor': bestand niet gekopieerd, of in de verkeerde map. Kijk met mpremote ls :lib.
  • Oude code draait nog: je kopieerde naar het bordje, maar er staat ook een oude .mpy die voorrang heeft. Verwijder hem.
  • ValueError: incompatible .mpy file: mpy-cross-versie past niet bij de firmware.

Oefenen

  1. Zet de mappenstructuur op met boot.py, main.py, config.py en een lege lib/sensor.py.
  2. Voeg het ontsnappingsluik toe bovenaan main.py en test het.
  3. Schrijf tools/upload.sh (of een .bat/PowerShell-versie) en gebruik hem.
  4. Meet met gc.mem_free() het vrije geheugen vóór en na import sensor, met .py en met .mpy.
Modelantwoord oefening 1

mpremote ls toont boot.py, main.py, config.py en de map lib/; mpremote ls :lib toont sensor.py.

Modelantwoord oefening 2

Houd BOOT ingedrukt, druk kort op EN (reset), laat BOOT na een seconde los. In mpremote zie je "veilige modus" en de prompt >>>.

Modelantwoord oefening 3

Zie Uploaden. Op Windows in PowerShell: foreach ($f in "boot.py","main.py","config.py") { mpremote cp $f ":$f" }.

Modelantwoord oefening 4
import gc
gc.collect(); voor = gc.mem_free()
import sensor
gc.collect(); print(voor - gc.mem_free(), "bytes")

Met een .mpy is het verschil kleiner, omdat de compilatie niet op het bordje gebeurt. Doe een reset tussen de metingen, want een module wordt maar één keer geïmporteerd.

Lokaal oefenen

Geen live lab. Werk met mpremote mount . zodat je na elke wijziging alleen main.py opnieuw hoeft te starten.

Samenvatting

  • Code in modules in lib/, instellingen in config.py, main.py als dirigent.
  • Een ontsnappingsluik bovenaan main.py voorkomt dat je buitengesloten raakt.
  • Upload met een script; ontwikkel met mount.
  • mpy-cross en const() besparen geheugen.
  • Volgende les: sensoren via I2C.

📝 Quiz: Projectstructuur: boot.py, main.py en modules

5 vragen · 70% nodig om te slagen

1. Waar zoekt MicroPython standaard naar modules?
2. Waarom een ontsnappingsluik bovenaan main.py?
3. Wat doet mpy-cross?
4. Wat doet const() uit micropython?
5. Waarom kan oude code nog draaien na kopiëren?