Programování s Raspberry Pi: Souběžné blikání LED

Tento kód ukazuje příklad práce s více vlákny v Pythonu. Každá LED bliká ve vlastním výpočetním vlákně, Kzatímco hlavní vlákno běží jako GUI tkinter. Postupně si vysvětlíme, co se děje v rámci běhu více vláken,


Základní princip více vláken

V Pythonu se běh více vláken řeší knihovnou threading. Každé vlákno běží souběžně s ostatními, ale vždy dělí ten samý proces. V našem případě:

  • Hlavní vlákno:
    • běží root.mainloop() – čeká na kliknutí myší, klávesnici apod.
  • Další vlákna (green_worker, yellow_worker, red_worker):
    • samostatně blikají svou LED bez toho, aby se GUI zaseklo.

Kdyby blikání bylo přímo v hlavním cyklu Tkinteru, program by se „zamrznul“ v sleep() a neakceptoval by žádné kliknutí. Více vláken umožňuje, že blikání běží na pozadí, GUI zůstává aktivní.


Vytvoření nového vlákna

Tahle konstrukce se v kódu objevuje pro každou LED:

pythongreen_worker = threading.Thread(target=worker_green, daemon=True)
green_worker.start()
  • target=worker_green znamená, že po spuštění vlákna bude spouštět funkci worker_green().
  • daemon=True označuje vlákno jako démon, tzn. když se ukončí hlavní program (např. při zavření okna), všechna vlákna démon automaticky skončí. To je vhodné pro blikání LED, aby neblikala navždy.
  • start() spustí vlákno.

Každá LED má tedy svoji kopii threading.Thread(target=worker_...), takže běží jako samostatné vlákno.


Komunikace mezi vlákny

Aby se blikání mohlo vypnout bezpečně, vlákna spolu komunikují. K tomu slouží threading.Event():

pythongreen_stop = threading.Event()
  • Event je jakýsi signál, který může být nastaven („set“) nebo ne (výchozí stav je „cleared“).
  • Uvnitř funkce worker_green():
pythondef worker_green():
while not green_stop.is_set():
green.on()
sleep(0.5)
green.off()
sleep(0.5)
  • green_stop.is_set() kontroluje, zda bylo blikání požádáno o ukončení.
  • Pokud tlačítko zavolá green_stop.set(), cyklus se vejde do podmínky is_set() a skončí.

Analogicky platí pro yellow_stop a red_stop.


Bezpečné zastavení vláken

Když uživatel klikne na tlačítko pro zastavení blikání, např. pro zelenou:

pythongreen_stop.set()
green_worker.join()
  • green_stop.set() nastaví signál. V další iteraci while ve worker_green() se cyklus přeruší.
  • green_worker.join() způsobí, že hlavní vlákno čeká, dokud green_worker neprojede return a vlákno skutečně skončí.

Díky join() je v kódu zajištěné, že nezůstanou „viset“ stará vlákna, když se další znovu spouštějí.


Bezpečnost vícevláknového řešení

  • Každé vlákno má svůj vlastní cyklus while a svůj vlastní sleep(). To znamená, že LED mohou blikat současně a nezávisle. Můžete i nastavit u každé LED jinou frekvenci blikání. V příkladu kódu je nastaveno blikání všech s opakováním 1 vteřina.
  • green.off(), yellow.off() apod. pracují přímo s GPIO, ale protože gpiozero je v tomto scénáři “bezpečně” navržený pro jednoduché aplikace (nepotřebuje synchronizaci kritických sekcí), může být použit i v více vláknech.
  • Změna stavu tlačítek (btn_green.config(...)) je už provedena v hlavním vlákně (Tkinter), takže GUI neovlivňují vlákna s blikáním, všechny změny jsou bezpečně řízeny z hlavního vlákna.

Výhody více vláken

  1. Každá LED může blikat vlastním frekvencí.
  2. Jedno vlákno může být vypnuto, druhé spuštěno, aniž by ovlivnilo ostatní.
  3. Tkinter může běžet v hlavním vlákně a reagovat na akce bez zpoždění.

Během práce s více vlákny je důležité si vždy pamatovat, že:

  • vlákna nemusí běžet „paralelně“ v pravém významu (kvůli GIL v Pythonu),
  • ale v praxi je to zde dostačující pro oddělení úkolů jako GUI a fyzické výstupy.

Celý kód

import tkinter as tk
from gpiozero import LED
from time import sleep
import threading

GREEN_PIN = 22
YELLOW_PIN = 27
RED_PIN = 17

green = LED(GREEN_PIN)
yellow = LED(YELLOW_PIN)
red = LED(RED_PIN)

green_worker = None
green_stop = threading.Event()

yellow_worker = None
yellow_stop = threading.Event()

red_worker = None
red_stop = threading.Event()


def blink_green():
    global green_worker, green_stop
    if green_worker is not None:
        green_stop.set()
        green_worker.join()
        green_worker = None
        green_stop.clear()
        green.off()
        btn_green.config(text="Bliká: zelená", bg="lightgray")
    else:
        green_worker = threading.Thread(target=worker_green, daemon=True)
        green_stop.clear()
        green_worker.start()
        btn_green.config(text="Stop zelená", bg="green")


def blink_yellow():
    global yellow_worker, yellow_stop
    if yellow_worker is not None:
        yellow_stop.set()
        yellow_worker.join()
        yellow_worker = None
        yellow_stop.clear()
        yellow.off()
        btn_yellow.config(text="Bliká: žlutá", bg="lightgray")
    else:
        yellow_worker = threading.Thread(target=worker_yellow, daemon=True)
        yellow_stop.clear()
        yellow_worker.start()
        btn_yellow.config(text="Stop žlutá", bg="yellow")


def blink_red():
    global red_worker, red_stop
    if red_worker is not None:
        red_stop.set()
        red_worker.join()
        red_worker = None
        red_stop.clear()
        red.off()
        btn_red.config(text="Bliká: červená", bg="lightgray")
    else:
        red_worker = threading.Thread(target=worker_red, daemon=True)
        red_stop.clear()
        red_worker.start()
        btn_red.config(text="Stop červená", bg="red")


def worker_green():
    while not green_stop.is_set():
        green.on()
        sleep(0.5)
        green.off()
        sleep(0.5)


def worker_yellow():
    while not yellow_stop.is_set():
        yellow.on()
        sleep(0.5)
        yellow.off()
        sleep(0.5)


def worker_red():
    while not red_stop.is_set():
        red.on()
        sleep(0.5)
        red.off()
        sleep(0.5)


def all_off():
    # vypneme všechna blikání a všechny LED
    flags = [green_stop, yellow_stop, red_stop]
    workers = [green_worker, yellow_worker, red_worker]
    for flag, worker, led, btn, color_text in zip(
        flags, workers, [green, yellow, red], [btn_green, btn_yellow, btn_red],
        ["Bliká: zelená", "Bliká: žlutá", "Bliká: červená"]
    ):
        if worker is not None:
            flag.set()
            worker.join()
        led.off()
        btn.config(text=color_text)


def on_close():
    all_off()
    root.destroy()


root = tk.Tk()
root.title("Více blikajících LED")
root.geometry("320x260")
root.resizable(False, False)

btn_green = tk.Button(root, text="Bliká: zelená", width=24, command=blink_green)
btn_yellow = tk.Button(root, text="Bliká: žlutá", width=24, command=blink_yellow)
btn_red = tk.Button(root, text="Bliká: červená", width=24, command=blink_red)
btn_all_off = tk.Button(root, text="Vypnout vše a zavřít", width=24, command=on_close)

btn_green.pack(pady=8)
btn_yellow.pack(pady=8)
btn_red.pack(pady=8)
btn_all_off.pack(pady=16)

root.protocol("WM_DELETE_WINDOW", on_close)
root.mainloop()

Napsat komentář

Vaše e-mailová adresa nebude zveřejněna. Vyžadované informace jsou označeny *

Time limit is exhausted. Please reload CAPTCHA.

Web používá Akismet ke snížení množství spamu. Zjistěte, jak jsou zpracovávány údaje z komentářů.