Seite 1 von 1

Code-Review - ESP32, Display, MicroPython

Verfasst: Freitag 16. Januar 2026, 12:42
von Dennis89
Hallo zusammen,

falls jemand von euch Lust hat, würde ich mich über ein Code-Review freuen.

Hardware:
- ESP32
- 1,4" ST7789 Display
- BME280 Temperatur-Sensor
- 2x Led's
- mechanischer Taster (Pull-down hardwareseitig)

Gewünschte Funktion:
- Bei Stromzufuhr Ablauf einer Willkommens-Animation
- Wechsel in den Home-Bilschirm. Alle 30 Sekunden soll der aktuellste Wert des Temperatursensors angezeigt werden
- Bei Betätigung des Tasters: Wechsel des Bildschirms. Simulation eines Einschaltvorgangs einer Maschine. Die grüne LED geht an, dabei erhöhen sich 4 Werte bis der Druckwert Nr 3 (hier machen die Nummern Sinn, weil die in "echt" tatsächlich so bezeichnet werden. Da das Display so klein ist, gibt es keine 1) seinen max. Wert überschreitet. Dann wird der Maschinenlauf simuliert. Dabei nehmen zwei Werte ab, der Druckwert schwankt und Temperatur 3 erhöht sich, bis sie über ihren max. Wert ist. Zur Warnung wird der Wert gelb angezeigt, die grüne LED geht aus, die rote geht an, alle Werte fallen ab und nach kurzer Zeit wird eine Fehlermeldung angezeigt. Nach einer kurzen Wartezeit geht die rote LED aus, der Bildschirm wechselt in den Home-Bildschirm und zeigt wieder die aktuellen Werte an, bis der Taster erneut gedrückt wird.

Während der Simulation soll der Taster keinen Einfluss haben. Eine weitere Simulation soll nur ausgeführt werden, wenn aus dem Home-Bildschirm heraus der Taster gedrückt wird.

Ich habe das umgesetzt und einige Stunden problemlos laufen lassen.

Ich habe Fragen:
- Kann es aus irgendeinem Grund zu einem Überlauf des Speichers führen? Muss/soll ich den garbage collector manuell, zyklisch aufrufen? Ich sehe keinen Grund dazu, das soll aber nichts heißen
- Kann ich das `°` - Symbole, das ich mit `0xF8` darstellen kann, irgendwie in den String mit reinpacken?

Ansonsten, die Funktion, die den Maschinenlauf animiert ist etwas lang, ich sehe aber keinen sinnvollen Punkt um die aufzuteilen. Eventuell könnte man die Größe und Breite des Bildes noch abfragen, ob die Position nicht anhand festen Werten zu bestimmen. Bin über jede Kritik und Verbesserung dankbar.

Code: Alles auswählen

from time import sleep, ticks_ms, ticks_diff
from machine import Pin, SPI, I2C
from _thread import start_new_thread
from collections import deque
import st7789
import bme280
import vga2_8x16 as font
from polygone import POLYGONE

RST_PIN = 17
CS_PIN = 22
DC_PIN = 5

SCL_PIN = 32
SDA_PIN = 33

BUTTON_PIN = 25
LED_RED_PIN = 26
LED_GREEN_PIN = 27 

HEIGHT = 135
WIDTH = 240

LOGO_BG = 0x5B94
#
# In milliseconds
UPDATE_CYCLE = 30 * 1e3

MAX_PRESSURE_2ND = 190
MAX_PRESSURE_3RD = 500
MAX_TEMPERATURE_2ND = 165
MAX_TEMPERATURE_3RD = 175

POLYGONE = [(int(x * 1.5), int(y * 1.2)) for x, y in POLYGONE]

class Monitor:
    def __init__(self, display, led_red, led_green, sensor_value):
        self.lcd = display
        self.led_red = led_red
        self.led_green = led_green
        self.sensor_value = sensor_value
        self.is_animation = False
        self._temperature = None

    @property
    def temperature(self):
        try:
            _temperature = self.sensor_value.pop()
            self._temperature = _temperature.split(" ")[0][:-1]
        except IndexError:
            pass
        return self._temperature

    def show_startup(self):
        center = (HEIGHT - 117) // 2
        self.lcd.jpg("Images/logo.jpg", 0, center, st7789.SLOW)
        sleep(0.3)
        for number in range(1, 6):
            self.lcd.jpg(f"Images/Eng{number}.jpg", 0, center, st7789.FAST)

    def show_home(self):
        self.lcd.fill(LOGO_BG)
        self.lcd.jpg("Images/home.jpg", 6, 0, st7789.SLOW)
        self.lcd.text(font, "500 bar", 167, 83, st7789.BLACK, st7789.WHITE)
        self.lcd.text(
            font, f"{self.temperature} C", 167, 107, st7789.BLACK, st7789.WHITE
        )
        self.lcd.text(font, 0xF8, 200, 107, st7789.BLACK, st7789.WHITE)

    def update_temperature(self):
        self.lcd.text(
            font, f"{self.temperature} C", 167, 107, st7789.BLACK, st7789.WHITE
        )
        self.lcd.text(font, 0xF8, 200, 107, st7789.BLACK, st7789.WHITE)

    def control_animation(self, _):
        if self.is_animation:
            return
        self.is_animation = True

    def animate_machine_running(self):
        self.led_green.on()
        self.lcd.jpg("Images/pid.jpg", 0, 0, st7789.SLOW)
        temperature_2 = temperature_3 = 20
        pressure_2 = pressure_3 = 0

        self.lcd.text(font, f"{pressure_2:.0f}bar", 14, 75, st7789.BLACK, st7789.WHITE)
        self.lcd.text(
            font, f"{temperature_2:.0f} C", 55, 112, st7789.BLACK, st7789.WHITE
        )
        self.lcd.text(font, 0xF8, 79, 112, st7789.BLACK, st7789.WHITE)
        self.lcd.text(font, f"{pressure_3:.0f}bar", 133, 71, st7789.BLACK, st7789.WHITE)
        self.lcd.text(
            font, f"{temperature_3:.0f} C", 135, 105, st7789.BLACK, st7789.WHITE
        )
        self.lcd.text(font, 0xF8, 159, 105, st7789.BLACK, st7789.WHITE)
        sleep(1)

        run_up = True
        while True:
            if run_up:
                pressure_3 += 31.5
                pressure_2 += 11.7
                temperature_2 += 9.3
                temperature_3 += 9
            else:
                pressure_2 -= 1.5
                temperature_2 -= 1
                temperature_3 += 1
                if temperature_3 > MAX_TEMPERATURE_3RD:
                    break
                if pressure_3 > MAX_PRESSURE_3RD:
                    pressure_3 -= 1.5
                else:
                    pressure_3 += 1
            self.lcd.text(
                font, f"{pressure_2:.0f}bar", 14, 75, st7789.BLACK, st7789.WHITE
            )
            self.lcd.text(
                font, f"{temperature_2:.0f} C", 55, 112, st7789.BLACK, st7789.WHITE
            )
            self.lcd.text(font, 0xF8, 79, 112, st7789.BLACK, st7789.WHITE)
            self.lcd.text(
                font, f"{pressure_3:.0f}bar", 133, 71, st7789.BLACK, st7789.WHITE
            )
            self.lcd.text(
                font, f"{temperature_3:.0f} C", 135, 105, st7789.BLACK, st7789.WHITE
            )
            self.lcd.text(font, 0xF8, 159, 105, st7789.BLACK, st7789.WHITE)
            if pressure_3 > MAX_PRESSURE_3RD:
                run_up = False
            sleep(1)

        self.lcd.fill_polygon(POLYGONE, 156, 111, st7789.YELLOW)
        self.lcd.text(
            font, f"{temperature_3 + 2:.0f} C", 135, 105, st7789.BLACK, st7789.YELLOW
        )
        self.led_green.off()
        self.led_red.on()
        for _ in range(5):
            sleep(0.8)
            pressure_3 -= 100
            pressure_2 -= 33
            temperature_2 -= 9
            temperature_3 -= 8
            self.lcd.text(
                font, f"{pressure_2:.0f}bar", 14, 75, st7789.BLACK, st7789.WHITE
            )
            self.lcd.text(
                font, f"{temperature_2:.0f} C", 55, 112, st7789.BLACK, st7789.WHITE
            )
            self.lcd.text(font, 0xF8, 79, 112, st7789.BLACK, st7789.WHITE)
            self.lcd.text(
                font, f"{pressure_3:.0f}bar", 133, 71, st7789.BLACK, st7789.WHITE
            )
            self.lcd.fill_polygon(POLYGONE, 156, 111, st7789.YELLOW)
            self.lcd.text(
                font, f"{temperature_3:.0f} C", 135, 105, st7789.BLACK, st7789.YELLOW
            )
            self.lcd.text(font, 0xF8, 159, 105, st7789.BLACK, st7789.YELLOW)

        self.lcd.jpg("Images/Error.jpg", 0, 14, st7789.SLOW)
        sleep(10)
        self.led_red.off()
        self.show_home()
        self.is_animation = False


def read_temperature_sensor(bme, sensor_value):
    while True:
        try:
            sensor_value.append(bme.temperature)
        except IndexError:
            # To ensure that the most up-to-date
            # temperature is always available
            sensor_value.pop()
            sensor_value.append(bme.temperature)
        sleep(30)


def main():
    i2c = I2C(scl=Pin(SCL_PIN), sda=Pin(SDA_PIN), freq=10000)
    bme = bme280.BME280(i2c=i2c)
    sensor_value = deque((), 1)
    button = Pin(BUTTON_PIN, Pin.IN)
    led_green = Pin(LED_GREEN_PIN, Pin.OUT)
    led_red = Pin(LED_RED_PIN, Pin.OUT)
    led_green.off()
    led_red.off()
    spi = SPI(2, baudrate=40000000, polarity=0, phase=0)
    display = st7789.ST7789(
        spi,
        HEIGHT,
        WIDTH,
        reset=Pin(RST_PIN, Pin.OUT),
        cs=Pin(CS_PIN, Pin.OUT),
        dc=Pin(DC_PIN, Pin.OUT),
    )
    display.init()
    display.rotation(1)
    display.fill(LOGO_BG)
    start_new_thread(read_temperature_sensor, (bme, sensor_value))
    monitor = Monitor(display, led_red, led_green, sensor_value)
    monitor.show_startup()
    sleep(2)
    monitor.show_home()
    button.irq(handler=monitor.control_animation, trigger=Pin.IRQ_RISING)
    start = ticks_ms()

    while True:
        if not monitor.is_animation:
            if ticks_diff(ticks_ms(), start) >= UPDATE_CYCLE:
                monitor.update_temperature()
                start = ticks_ms()
        else:
            monitor.animate_machine_running()
        sleep(0.1)


if __name__ == "__main__":
    main()
Grüße
Dennis

Re: Code-Review - ESP32, Display, MicroPython

Verfasst: Donnerstag 22. Januar 2026, 11:45
von __blackjack__
Mir ist die Ausnahmebehandlung bei `read_temperature_sensor()` unklar. Beim `append()` kann doch gar kein `IndexError` auftreten. Und falls der beim abfragen von `bme.temperature` auftritt, dann kann der ja auch im ``except``-Zweig auftreten‽ Dort ist der `pop()`-Aufruf überflüssig, falls die maximale Länge immer 1 ist. Falls nicht verstehe ich den Schritt an der Stelle nicht.

Re: Code-Review - ESP32, Display, MicroPython

Verfasst: Donnerstag 22. Januar 2026, 13:19
von DeaD_EyE
Möglicherweise wirft bme.value den IndexError. Ohne zu wissen, welche Bibliothek verwendet wird, kann man keine Aussage darüber treffen.

Die Temperatur kann man doch einfach einem Attribut der Klasse zuweisen.
Da die Abfrage in einem Thread läuft, besteht eine Race Condition, wenn man den Wert einem Attribut der Klasse zuweist. Da könnte man ggf. ein Lock verwenden.

https://docs.python.org/3.5/library/_th ... ocate_lock

Re: Code-Review - ESP32, Display, MicroPython

Verfasst: Donnerstag 22. Januar 2026, 13:20
von Dennis89
Hi,

In der MP Doku steht, dass ein `IndexError` aufgerufen wird, wenn es kein Platz mehr hat. Da ich die Länge auf 1 beschränkt habe, hat es nach meinem Verständnis nur Platz, wenn vor dem nächsten Auslegen des Sensor, der Wert geholt wird. Während der Animation wird allerdings kein Wert geholt. Nach der Animation hätte ich aber gerne den aktuellen Wert des Sensors (auch wenn das niemand merken würde). Daher dachte ich mir, der `IndexError` kann nur entstehen, wenn die `deque` voll ist und dann kann ich die in der Ausnahmebehandlung die `deque` mit `pop()` leeren und mit dem aktuellen Wert befüllen.

Grüße
Dennis

Re: Code-Review - ESP32, Display, MicroPython

Verfasst: Donnerstag 22. Januar 2026, 13:26
von DeaD_EyE
Doku vorsichtig lesen:

class collections.deque(iterable, maxlen[, flags])

- The optional flags can be 1 to check for overflow when adding items.

append(x)
Add x to the right side of the deque. Raises IndexError if overflow checking is enabled and there is no more room in the queue.


Overflow-Checking ist bei dir nicht aktiv, aber die Länge auf 1 limitiert. D.h. der IndexError darf nicht auftreten und falls doch, dann ist die Doku falsch oder du hast einen Bug gefunden.

Re: Code-Review - ESP32, Display, MicroPython

Verfasst: Donnerstag 22. Januar 2026, 17:49
von Dennis89
Oh, das habe ich übersehen. Danke, werde ich noch anpassen bzw, die flag setzen.

Grüße
Dennis

Re: Code-Review - ESP32, Display, MicroPython

Verfasst: Freitag 23. Januar 2026, 17:22
von __blackjack__
@Dennis89: Das Flag zu setzen macht nicht wirklich Sinn. Du willst da doch immer den letzten Wert haben, also das jeder neue Wert den alten verdrängt.

Re: Code-Review - ESP32, Display, MicroPython

Verfasst: Samstag 24. Januar 2026, 09:50
von Dennis89
Ich sollte die Doku echt aufmerksamer lesen. Nach `max_len` habe ich aufgehört, dabei steht da ja, dass der Alte durch den Neuen verdrängt wird. Und dann kann mein ganzes `try`/`except` natürlich raus.

Danke und Grüße
Dennis