DVA-Praktikum Gruppe 5

Technische Hochschule Augsburg
Fakultät für Informatik
Veranstaltung DV-Anwendungen (Dr. Ing. Volodymyr Brovkov)
Sommersemester 2025

Authors

Jessica Schach, jessica.schach@tha.de

Meryem Ünüvar, meryem.uenuevar@tha.de

Naqeebah Binti Mohd Zaini, naqeebah.binti.mohd.zaini@tha.de

Jack Chamoun, jack.chamoun@hs-augsburg.de

Published

March 30, 2025

1. Selenium

1.1 Einleitung

Das Thema der Transparanz über den aktuellen Notenstand ist für viele Studenten ein wichtiger Bestandteil ihrer akademischen Laufbahn. Besonders uns als Studenten der Technischen Hochschule Augsburg, fehlt es an einer Möglichkeit den aktuellen Notendurchschnitt bequem online einzusehen. Um diese Lücke zu füllen, haben wir uns entschieden eine eigene Lösung zu entwickeln. Die Umsetzung des Projektes erfolgte mithilfe von Selenium und einem einfachen Python-Projekt, welches das PDF-Dokument ausliest und anhand der darin enthaltenen Noten und ECTS-Punkten, den aktuellen Notendurchschnitt berechnet und die Summe der gesammelten ECTS-Punkten ausgibt.

In diesem Bericht wird sowohl auf die technische Umsetzung des Projekts eingegangen als auch auf die Nutzung von Selenium zur Interaktion mit dem Browser, um die notwendigen Daten aus einer PDF-Datei zu extrahieren. Dabei soll das Projekt nicht nur eine praktische Lösung für uns Studierende bieten, sondern auch aufzeigen, wie mächtig Automatisierungstools wie Selenium in der Praxis eingesetzt werden können.

Aufgabenverteilung

An diesem Projekt sind vier Personen beteilig: Jessica Schach, Jack Chamoun, Meryem Ünüvar und Naqeebah Binti Mohd Zaini. Die Aufgaben wurden folgendermaßen untereinander aufgeteilt:

Jessica Schach:

  • Einrichtung von Quarto (ca. 1h)
  • Erstellung des Berichts inklusive regelmäßiger Anpassungen (ca. 5h)

Jack Chamoun:

  • Recherche
  • Implementierung der ersten Version des Projekts
  • Aufteilung der Aufgaben
  • Gesamtaufwand ca. 8h

Meryem Ünüvar:

  • Recherche und Implementierung der Steps (ca. 4h)
  • Erstellung einer README für Selenium (ca. 1h)
  • Vorbereitung der Präsentation (1,5h)

Naqeebah Binti Mohd Zaini:

  • Recherche und Implementierung der Notendurchschnittsberechnung (ca. 3h)
  • Erweiterung aller vordefinierten Methoden von Jack inklusive Recherche(ca. 3h)

1.2 Grundlagen

1.2.1 Selenium

Bei Selenium handelt es sich um ein Open-Source-Framework, welches ermöglicht, Webanwendungen automatisiert zu steuern, indem es Benutzerinteraktionen im Browser simuliert. Entwickelt wurde es erstmals als “JavaScriptTestRunner” bei dem Technologieunternehmen ThoughtWorks in Chicago im Jahr 2004. Beteiligt war unter anderem Jason Huggins, welcher den JavaScriptTestRunner für den Test einer interen Zeit- und Kostenanwendung entwickelt hat. Zusammen mit Paul Gross und Jie Tina Wang wurde das Projekt im Laufe der Zeit zum Selenium-Framework weiterentwickelt.

Die Anwendung von Selenium findet hauptsächlich Platz im Bereich des automatisierten Testens von Webanwendungen sowie bei der Automatisierung von monotonen webbasierten Verwaltungsaufgaben. Zudem kann Selenium für Web-Scraping benutzt werden. Web-Scraping ist eine Technik, um automatisiert Daten von Webseiten zu extrahieren.

1.2.2 Python

Python ist eine der beliebtesten Programmiersprachen weltweit. Sie wurde in den frühen 1990er-Jahren von Guido van Rossum entwickelt und zeichnet sich durch ihre einfache und leicht verständliche Syntax aus. Sie findet vor allem Anwendung in den Bereichen Webentwicklung, Datenanalyse, Automatisierung sowie wissenschaftliche Berechnungen.

1.2.3 Notenschnittberechnung

In höheren Bildungseinrichtungen ist es üblich, dass die Noten anhand der sogenannten ECTS-Punkte gewichtet werden.

Wichtig zu beachten ist zudem, dass an der Technischen Hochschule Augsburg - unter anderem in dem Studiengang Informatik - die Noten der ersten zwei Halbjahren nur halb so viel gewichtet werden wie die Noten von den anderen Semestern. Dies wurde ebenfalls berücksichtigt.

Daher wurde folgende Formel für die Berechnung des Gesamtnotendurchschnitts verwendet:

\[ \text{Ø-Note} = \frac{\sum\limits_{k} \left( \sum\limits_{i=1}^{2} (N_{k,i} \cdot E_{k,i} \cdot 0.5) + \sum\limits_{j=3}^{7} (N_{k,j} \cdot E_{k,j}) \right)}{\sum\limits_{k} \left( \sum\limits_{i=1}^{2} (E_{k,i} \cdot 0.5) + \sum\limits_{j=3}^{7} E_{k,j} \right)} \]

Legende zur Formel der Ø-Note

  • Ø-Note: Der gewichtete Gesamtnotendurchschnitt über alle Semester und Module hinweg.
  • k: Index für die einzelnen Module bzw. Veranstaltungen.
  • i = 1, 2: Die ersten beiden Semester (werden mit halbem Gewicht berücksichtigt).
  • j = 3, 4, 5, 6, 7: Die folgenden Semester (werden voll gewichtet).
  • Nₖ,ᵢ / Nₖ,ⱼ: Die Note des Moduls k im Semester i bzw. j.
  • Eₖ,ᵢ / Eₖ,ⱼ: Die Anzahl der ECTS-Punkte für das Modul k im Semester i bzw. j.

1.3 Projekt

1.3.1 Vorbereitung der Daten

Für die Vorbereitung der Daten wurde Web-Scraping mit Selenium verwendet. Als Daten wird erstmal hier die PDF-Datei beschrieben, auf die über das Prüfungsportal HIS zugegriffen werden kann. Diese PDF beinhaltet die Noten inklusive ihrer ECTS-Punkte.

Selenium ermöglicht es die Browserinteraktionen zu simulieren, welche notwendig sind, um die PDF-Datei herunterzuladen. Die Nutzung von Selenium benötigt einen Webbrowser inklusive des jeweiligen Webdrivers. Die Wahl des verwendeten Webbrowsers fiel in diesem Projekt auf Chrome, da dieser mit den JavaScript-DOM-Suchfunktionen am kompatibelsten und unter den Benutzern gebräuchlich ist. Für die Bereitstellung des passenden Drivers zum jeweiligen Betriebssystem trägt das Paket webdriver-manager die Verantwortung. Dazu muss der ChromeDriverManager von webdriver-manager als Handler der Driverinstanz übergeben werden.

webdriver.Chrome(service=Service(ChromeDriverManager().install()), options=options)

Da der Chrome-Driver eine anwendungsspezifische Funktion hat, wird eine Klasse erzeugt, welche die benötigten Schritte in seinen Methoden ausführt.

class PdfExtractor:                             # Schritte: 
    def get_pdf(self):                          
        webdriver = AdminTHAWebDriver()         # Anwendungsspezifische Driver-Instanz 
        webdriver.open_website()                # (1)
        webdriver.fill_credentials()            # (2-4)
        webdriver.go_to_download_page()         # (5-6)
        webdriver.download_pdf()                # (7)
        webdriver.close_session() 

Schritte:

  1. Öffne die Webseite des Prüfungsportals.
  2. Tippe den Benutzernamen ein.*
  3. Tippe das Passwort ein.*
  4. Klicke auf den Button Anmelden.
  5. Klicke auf Prüfungsverwaltung.
  6. Klicke auf Notenspiegel.
  7. Klicke auf den Button PDF.

* Zum Eintippen von Benutzernamen und Passwort, muss zunächst das DOM-Element basierend auf einen CSS-Selektor gesucht und dann der Input als keys übergeben werden. Diese Informationsübergabe funktioniert nur bei klassischen Input-Elementen:

# pass your username
username_input = self.driver.find_element(By.ID, "asdf")
username_input.clear()
username_input.send_keys(Data.USERNAME)

# pass your password
pw_input = self.driver.find_element(By.ID, "fdsa")
pw_input.clear()
pw_input.send_keys(Data.PW)

Die benötigten Anmeldeinformationen werden in einer .env-Datei gepeichert und sollten nicht weitergegeben werden. Die Erstellung dieser Datei wird in der README.md nochmal genauer erklärt.

1.3.2 Datenanalyse und -verarbeitung

Für die Verarbeitung der Daten wurde Python verwendet. Mit Hilfe der Python Bibliothek pdfplumber konnte die PDF-Datei - welche über Web-Scraping mit Selenium heruntergeladen wurde - analysiert werden. So wurden die Noten sowie die dazugehörigen ECTS-Punkte aus der PDF-Datei herausgefiltert und anschließend für die Berechnung des Gesamtnotendurchschnitts verwendet.

Die Aufgabe der Datenanalyse und -verarbeitung erfüllt die Klasse Calculator.

class Calculator:
    def __init__(self, pdf_path):
        self.pdf_path = pdf_path
        self.all_rows = []
        self.data = []

Die wichtigsten Methoden dieser Klasse sind die Notendurchschnittberechnung sowie die Summen-Berechnung der ECTS-Punkte. Diese Funktionalitäten wurden wie folgt implementiert:

def calculate_ects_sum(self):
    """Calculates the total sum of ECTS credits"""
    return sum(row[2] for row in self.data)

def calculate_average_grade(self):
    """Calculates the weighted average grade"""
    sum_of_grades = 0
    sum_of_ects = 0
    for row in self.data: 
        try:
            grade = float(row[1])
            subject_code = int(row[0])//10
            if (int(subject_code) // 1000 == 397) & (subject_code % 100 <= 10):
                sum_of_grades += (grade * row[2] * 0.5)
                sum_of_ects += (row[2] * 0.5)
            else:
                sum_of_grades += (grade * row[2])
                sum_of_ects += (row[2])
        except ValueError:
            continue
    return round((sum_of_grades/sum_of_ects), 3)

1.3.4 Ausführung

Die Ausführung des Projekts ist detaillierter in der RADME.md beschrieben.

Um den Gesamtnotendurchschnittberechner nutzen zu können, müssen Python (in der Version 3.10 oder höher) sowie das Google Chrome Binary installiert sein. Wenn diese installiert sind, ist eine Datei mit dem namen .env anzulegen. Sie beinhaltet die RZ-Anmeldedaten in folgender Form:

WEB_USERNAME="<dein Username>"
WEB_PW="<dein Passwort>"

Schließlich installiert der Befehl make alle notwendigen Python Libraries und führt das Programm aus.

Bei diesem Projekt handelt es sich um ein kleines CLI-Tool, welches ohne Erstellung eines aufwendigen Frontends erfolgte. Die Ausgabe sieht wie folgt aus:

😎 Downloading pdf-file. It may take some seconds... 
Created TensorFlow Lite XNNPACK delegate for CPU.
File downloaded successfully: C:\Users\Jessica\Downloads\Kontoauszug_gesamt675d104b-7c79-4518-bd23-6f296fbe36ab.pdf. See in downloads dir
The total sum of your ECTS credits is 145.0.
Your average grade is 1.82.

1.3.5 Probleme

Wie fast jedes Projekt, brachte auch der Notendurchschnittberechner einige Herausforderungen mit sich.

Zum Einen trat der Sicherheitsaspekt von personenebezogenen Daten - wie der Benutzername und das Passwort - in den Vordergrund. Diese sensiblen Daten können selbstverständlicherweise nicht einfach in den Code aufgenommen werden. Unsere Lösung für dieses Problem ist eine .env-Datei, welche von dem Nutzer des Berechners angelegt werden muss. Diese Datei enthält die RZ-Anmeldedaten, welche als Umgebungsvariabeln gespeichert werden. Die .env-Datei steht in der .gitignore, sodass diese nicht versehentlich in GitLab gepusht werden kann. Selenium nutzt dann diese Umgebungsvariablen, um die Anmeldung durchzuführen.

Eine weitere Herausforderung ist ein bekanntes Selenium-Problem bezüglich der zeitlichen Synchronisierung des Ladens von Webkomponenten. Beispielsweise darf Selenium die Browser-Session nicht schließen bevor der Dowload-Vorgang abgeschlossen ist. Dies wurde mit der time.sleep()-Funktion gelöst. Die Verzögerung mit der Funktion hat ebenfalls den Vorteil, dass der Download nicht ständig durch Lesezugriffe im Download-Ordner unterbrochen wird und schneller ablaufen kann, indem ein festes Zeitfenster bereitgestellt wurde.

# Start the download
time.sleep(10) 
# Check if the download is complete
# Copy the file in the project

1.4 Fazit

Selenium hat sich als äußerst leistungsfähiges Tool für die Automatisierung von Browserinteraktionen erwiesen. Im Rahmen unseres Projekts konnten wir mit seiner Hilfe eine effiziente Lösung zur Notenauswertung entwickeln, die den manuellen Aufwand für Studierende erheblich reduziert. Besonders die Möglichkeit, wiederkehrende Webinteraktionen zu automatisieren, hat sich als praktisch erwiesen. Dennoch brachte die Nutzung von Selenium auch Herausforderungen mit sich, insbesondere in Bezug auf Sicherheitsaspekte und Synchronisationsprobleme beim Laden von Webseiten. Diese konnten jedoch durch die Verwendung von .env-Dateien zum Schutz sensibler Daten sowie durch den gezielten Einsatz von time.sleep() zur Steuerung des Seitenaufbaus erfolgreich bewältigt werden. Insgesamt zeigt unser Projekt, dass Selenium ein wertvolles Werkzeug für Web-Scraping und Automatisierungsaufgaben ist, insbesondere in Kombination mit Python und weiteren Bibliotheken zur Datenverarbeitung.

1.5 Quellen

2. Wetterapp mit Leptos

2.1 Einleitung

Die Systemsprache Rust überzeugt aktuell durch ihre Performanz, Speicher - und Typsicherheit. Sie ersetzt immer mehr bekannte C-Kommandozeilenprogramme, wie ls durch exa oder grep durch ripgrep. Weniger bekannt ist die Präsenz von Rust-Frameworks in der Webprogrammierung, wie zum Beispiel leptos. Die Schnelligkeit und Ansprechbarkeit einer Website mit Leptos wollen wir anhand einer Wetter-App zeigen.

Die App soll das aktuelle Wetter und das Wetter der nächsten 5 Tage in dreistündigen Zeitabständen zeigen.

Aufgabenverteilung

Jessica Schach:

  • README.md schreiben (1 h)
  • Einrichtung Rust, Leptos (1 h)
  • Backend (8 h)
  • Refactoren des Frontends mit Naqeebah (4h + 4h = 8h)
  • Anpassung des backends an das frontend (1 h)
  • Gesamtaufwand: 19 h

Jack Chamoun:

  • Lokale Einrichtung von WeatherApp (2 h)
  • Current API einsetzen (4 h)
  • API Response filtern und anpassen (4 h)

Meryem Ünüvar:

  • Berichtverfassung (5 h)

Naqeebah Binti Mohd Zaini:

  • Figma Wireframes erstellen (4 h)
  • Versuch Frontend (ohne Bulma) (8 h)
  • Versuch Frontend (mit Bulma) (11 h)

2.2 Grundlagen

Die Grundlagen über Rust, Leptos, Trunk, Bulma und OpenWeather API werden kurz erläutert.

2.2.1 Rust

Rust ist eine kompilierte System-Sprache, die mehreren Paradigmen zugeordnet werden kann: objektorientiert, nebenläufig, funktional, imperativ und strukturiert. Sie wurde 2010 von Graydon Hoare entworfen und zusätzlich von ihm, Mozilla und der Rust-Stiftung entwickelt. Der Name “Rust” stammt von einer Pilzgruppe, die für ihre Resilienz und ausgeprägte Überlebensfähigkeiten bekannt ist. Alle gängigen Betriebssystem unterstützen Rust und seit Dezember 2022 ist sie auch im Linux Kernel eingebaut. Wichtige Merkmale von Rust sind, dass sie stark und statisch typisiert ist aber denoch eine dynamische Typinferenz durch z.B. “Object Traits” erlaubt. Dies erklärt zum Teil auch die hohe Performanz, da zur Laufzeit keine Typbestimmung erfolgt. Eine weitere Besonderheit ist die Speicherverwaltung durch “Ownership”-Regeln. Während viele Sprachen “Garbage Collection” nutzen, um Speicher automatisch zu bereinigen, muss der Programmierer dafür sorgen, dass bestimmte Regeln eingehalten werden. Die beliebten Regeln sind grob:

  • Jede Variable hat einen Besitzer
  • Eine Variable kann zu einem Zeitpunkt nur einen Owner besitzen
  • Wenn der Owner nicht mehr im Scope (z.B. Funktion oder {}) ist, dann wird die Variable ungültig.

Zusätzliche automatische Sicherheitschecks durch den Compiler führen zum Beispiel auch, dass nicht-exisitierende Inidizes von Arrays nicht gelesen werden können.

2.2.2 Leptos

Leptos ist ein deklarativer Full-Stack Rust-Framework. Es ermöglicht isomorphische Server-Funktionen, die zwar immernoch ausschließlich auf dem Server laufen aber nicht vom Client Code getrennt geschrieben werden müssen (z.B. durch “use client”). Das Framework baut auf den Web-Standards auf, sodass man HTML-DOM Elemente, wie gewohnt nutzen kann. Durch Signals kann man dynamisch Werte im HTML-Code updaten, sodass direkte Änderungen einfach möglich sind. All diese Features ermöglichen es eine performante Website zu entwickeln.

2.2.3 Trunk

Trunk ist ein Tool, das Rust-Webanwendungen in WebAssembly (.wasm) kompiliert. Es bindet CSS-, JS- und andere Dateien (wie Bilder, Audios und Videos) automatisch in das Projekt ein. Trunk übernimmt die Aufgabe, die Anwendung zu bauen, zu bündeln und zu servieren, wodurch der Entwicklungsprozess erheblich vereinfacht wird. Es unterstützt Hot-Reloading, was bedeutet, dass Änderungen am Code sofort im Browser sichtbar sind, ohne dass die Anwendung manuell neu gestartet werden muss. Dies macht Trunk zu einem unverzichtbaren Werkzeug für die Entwicklung moderner Rust-Webanwendungen. Trunk nutzt das Crate wasm-bindgen, weshalb es zusätzlich noch installiert werden muss.

2.2.4 Bulma

Bulma ist ein modernes, leichtgewichtiges CSS-Framework, das auf Flexbox basiert. Es bietet eine Vielzahl von vorgefertigten Klassen, die es Entwicklern ermöglichen, schnell und einfach responsive und ansprechende Benutzeroberflächen zu erstellen. Bulma ist modular aufgebaut, sodass nur die benötigten Komponenten in ein Projekt eingebunden werden müssen, was die Ladezeiten reduziert. Es unterstützt eine klare und intuitive Syntax, die die Entwicklung von Layouts und Designs erheblich vereinfacht. In unserem Projekt wird Bulma verwendet, um das Frontend der Wetter-App zu gestalten und eine benutzerfreundliche Oberfläche zu gewährleisten.

2.2.5 OpenWeather API

OpenWeather ist eine Plattform, die verschiedene One Call APIs für Wetterdaten anbietet. Sie bietet kurzfristige und langfristige Wetterprognosen, -historien und -beobachtungen von allen Orten auf der Welt auf die Minute genau an. Täglich sind bis zu 1000 API calls kostenlos. In unserem Projekt verwenden wir zwei APIs:

  • Current Weather Data: Aktuelles Wetter (JSON)
  • 5 Day/ 3 Hour Forecast: Wetter in 5 Tagen und in dreistündigen Abständen (JSON)

2.2.6 Figma

Figma ist eine Software, welche für die Erstellung von Benutzeroberflächen und Prototypen verwendet wird. Mithilfe von Figma haben wir die UI von unserer Wetter App entworfen.

2.3 Projekt

Das Projektverzeichnis ist wie folgt aufgebaut:

├── resources 
│   ├── bulma
│   ├── style.css
├── src
│   ├── view
│   │   ├── current_info.rs
│   │   ├── current_weather.rs
│   │   ├── description.rs
│   │   ├── five_days_forecast.rs
│   │   ├── icons.rs
│   │   ├── location_date.rs
│   │   ├── search_bar.rs
│   │   ├── three_hours_forecast.rs
│   │   ├── mod.rs
│   ├── api.rs
│   ├── lib.rs
│   ├── main.rs
├── .gitignore
├── Cargo.lock
├── Cargo.toml
├── README.md
└── index.html

2.3.1 Backend

Fetch Data

Die Wetterdaten werden von der WeatherAPI mit einem GET-Befehl angefragt. Der Rückgabewert ist eine JSON-Datei, die bei uns als struct Forecast gespeichert ist. Die Anfrage erfordert einen spezifischen Stadtnamen und einen API-Key.

pub async fn fetch_data(city: String) -> Result<Forecast, Box<dyn Error>> {

    let url = format!("https://api.openweathermap.org/data/2.5/forecast?q={city}&appid=8050aa94a2294f0b93862b769bdbaf8a&units=metric"); 
    let response = Request::get(&url)
        .send()
        .await?;

    if !response.ok() {
        return Err(format!("Error fetching data: {}", response.status()).into());
    }
    
    let data: Forecast = response.json().await?;

    Ok(data)
}

Speicherung der Daten mit serde (api.rs)

Mit dem trait Deserialize vom Crate Serde kann man zum Beispiel JSON-Dateien in eine Rust Datenstruktur umwandeln. Deserialize ist auch als derive Makro verfügbar und kann einfach eingesetzt werden.

#[derive(Deserialize, Debug, Clone)]
pub struct CurrentForecast {
    pub weather: Vec<Weather>,
    pub main: Main,
    pub visibility: i32,
    pub wind: Wind,
}

#[derive(Deserialize, Debug, Clone)]
pub struct Forecast {
    pub city: City,
    pub list: Vec<Day>,
}

#[derive(Deserialize, Debug, Clone)]
pub struct Day {
    pub dt_txt: String,
    pub main: Main,
    pub weather: Vec<Weather>,
}

#[derive(Deserialize, Debug, Clone)]
pub struct Main {
    pub temp: f64,
    pub feels_like: f64,
    pub temp_min: f64,
    pub temp_max: f64,
    pub humidity: i32,
}

#[derive(Deserialize, Debug, Clone)]
pub struct Weather {
    pub main: String,
    pub description: String,
}

#[derive(Deserialize, Debug, Clone)]
pub struct Wind {
    pub speed: f64,
}

#[derive(Deserialize, Debug, Clone)]
pub struct City {
    pub country: String,
}

2.3.2 Frontend

Im view Ordner werden die Komponenten der UI gerendert. Beispielsweise wird die Anzeige der Daten in 5 Tagen mit der Funktion Days realisiert: Für jeden Eintrag wird eine Day-Element ausgegeben mit den entsprechenden Wetterdaten.

Request stellen

Mit Hilfe von LocalResource werden asynchrone und reaktive Anfragen an das Backend gestellt, das heißt der Compiler überprüft den Zustand der Anfrage und aktualisiert den veränderten Zustand.

let current_weather_resource = LocalResource::new(move || {
    let city = city.clone();
    async move {
        let city_name = city.get();
        fetch_current_data(city_name).await.ok()
    }
});

let weather_resource = LocalResource::new(move || {
    let city = city.clone();
    async move {
        let city_name = city.get();
        fetch_data(city_name).await.ok()
    }
});

Structs für unsere Bedürfnisse (lib.rs)

Wir haben für die View benutzerdefinierte structs erstellt.

Beispiel: ThreeHourForecast (Linke Spalte in der View)

#[derive(Clone, Debug)]
pub struct ThreeHoursForecast {
    // Three Hours Forecast
    pub hour: u8,
    pub temperature: f64,
    pub condition: String,
}

Füllen der Structs mit Daten

Die angefragten Daten nutzen wir, um unsere benutzerdefinierten structs zu befüllen. Effect ist ein Feature vom leptos Crate, dass ein reaktives Statemanagement ermöglicht. Veränderungen von bestimmten Typen, wie RwSignal oder Futures innerhalb diesen Bereichs werden erfasst (zum Beispiel, wenn sich der API-Anfragestatus ändert) und die View wird aktualisiert. Dadurch wird uns die Aufgabe abgenommen den Zustand manuell in allen Komponenten bzw. Views zu ändern, da der Compiler über alle Bereich scannt und das veränderte Signal an der entsprechenden Stelle aktualisiert.

Beispiel: ThreeHourForeCast

Effect::new(move || {
    match weather_resource.get().as_deref() {
        Some(Some(data)) => {

            country.set(data.city.country.clone());

            // Next 9 Entries
            let mut three_hour_entries = Vec::new();
            for entry in data.list.iter().take(9) {
                let hour: u8 = entry.dt_txt[11..13].parse().unwrap_or(0);
                let condition = entry.weather.get(0)
                    .map_or("N/A".to_string(), |w| format!("{}", w.main));
                let temperature = entry.main.temp.clone();

                three_hour_entries.push(ThreeHoursForecast {
                    hour,
                    condition,
                    temperature,
                });
            }
            three_hours_forecast.set(three_hour_entries);
            
            // Weitere Folgen hier noch
        }
    };
})

Weitergabe aller Daten an die View

Alle structs mit ihren Daten werden an die View übergeben und an den entsprechenden Stellen zu dem die Attributnamen matchen eingefügt. Das heißt die leere View erhält Leben.

view! {
    <WeatherApp 
        city 
        country
        three_hours_forecast
        current_weather
        current_info
        five_days_forecast
    />
}

Einbindung aller Komponenten in einer View (view/mod.rs)

Die View ist ein einziges Interface, dass aus mehreren Komponenten besteht. Die Hauptkomponente WeatherApp umfasst alle Subkomponenten. Mit der view! Makro wird eine HTML-Seite mit all den übergebenen Daten generiert.

#[component]
pub fn WeatherApp(
    city: RwSignal<String>,
    country: RwSignal<String>,
    three_hours_forecast: RwSignal<Vec<ThreeHoursForecast>>,
    current_weather: RwSignal<CurrentWeather>,
    current_info: RwSignal<CurrentInformation>,
    five_days_forecast: RwSignal<BTreeMap<String, FiveDaysForecast>>,
) -> impl IntoView {

    view! {
        <div class="container pt-6">
            <div class="fixed-grid has-5-cols">
                <div class="grid">
                    <ThreeHoursForecastComponent three_hours_forecast city_name=city/>
                    <LocationDate city country />
                    <CurrentWeatherComponent current_weather />
                    <Description current_weather />
                    <CurrentInfo current_info />
                    <FiveDaysForecastComponent five_days_forecast city_name=city />
                    <SearchBar city />
                </div>
            </div>
        </div>
    }
}

Aufbau einer Komponente (search_bar.rs)

Beispiel: SearchBar

Die SearchBar wird genutzt, um eine bestimmte Stadt für die Wettervorhersage zu definieren. Die Variable city ist vom Typ RwSignal, das bei einer Änderung mit set() oder update() reaktiv auch die View mit dem neuen Wert aktualisiert. Der Rückgabewert ist ein trait IntoView, der eine View Komponente zurückgibt, die dann gerendert werden kann.

#[component]
pub fn SearchBar(city: RwSignal<String>) -> impl IntoView {

    let input = RwSignal::new(String::new());

    view! {
        <div class="cell is-col-start-5 is-row-start-1">
            <div class="control glass has-icons-left has-icons-right">
                <input 
                    class="input" 
                    type="text" 
                    placeholder="Search for a city" 
                    on:input=move |e| {
                        let value = event_target_value(&e);
                        input.set(value);
                    }
                    on:keydown=move |e| {
                        if e.key() == "Enter" {
                            city.set(input.get()); 
                        }
                    }
                />
                <Search />
            </div>
        </div>
    }
}

2.4 Fazit

Leptos hat sich als ein leistungsstarkes Framework für die Entwicklung moderner Webanwendungen in Rust erwiesen. Die deklarative und reaktive Programmierung ermöglicht eine effiziente und intuitive Entwicklung, während die Integration von Webstandards und die Unterstützung für isomorphische Server-Funktionen die Flexibilität erhöhen. Besonders beeindruckend war die einfache Handhabung von asynchronen Datenanfragen und das reaktive State-Management, das Änderungen automatisch in der Benutzeroberfläche widerspiegelt. Trotz der steilen Lernkurve und der noch begrenzten Community-Ressourcen bietet Leptos eine vielversprechende Grundlage für performante und sichere Webanwendungen. Unser Wetter-App-Projekt hat gezeigt, dass Leptos eine ausgezeichnete Wahl für Projekte ist, bei denen Geschwindigkeit und Effizienz im Vordergrund stehen.

2.5 Probleme

Auch die Besonderheit einer WSL-Umgebung erforderte ein nicht-standardgemäßes Request-Crate, nämlich gloonet anstatt reqwest. Der Grund hierfür war das “Sub-Crate” mio, welches von reqwest verwendet wird, denn dieses nutzt OS-Features für die Asynchronität der Anfragen. Da kommt das Crate durcheinander, da sowohl Windows als auch Linux als Betriebssysteme erkannt werden. Die Besonderheit einer WSL-Umgebung erforderte ein nicht-standardgemäßes Request-Crate, nämlich gloonet anstatt reqwest.

2.6 Quellen

3. ESP-32 Web Server

3.1 Einleitung

Dieses Projekt stellt ein ESP32-basiertes Mikrocontroller-Setup vor, das Raumtemperaturdaten mit einem BMP280-Sensor ausliest und sie über ein lokales Netzwerk via HTTP bereitstellt. Die gemessene Temperatur wird im JSON-Format über eine einfache RESTful-Schnittstelle zugänglich gemacht. Ziel ist es, dieses eingebettete System als lokale Datenquelle in eine zuvor entwickelte Wetteranwendung auf Basis von Rust und Leptos zu integrieren. Dadurch kann die App nicht nur externe Wetterdaten anzeigen, sondern auch aktuelle Raumtemperaturwerte in Echtzeit mit einbeziehen.

Aufgabenverteilung

Meryem Ünüvar:

  • Temperatur-Sensor ansteuern und WebServer mit ESP32 aufbauen: 15 Stunden

Jessica Schach:

  • Erweiterung Leptos Projekt: 2 Stunden
  • Anpassung: 0.5 Stunden

Naqeebah Zaini:

  • Berichtverfassung: 2.5 Stunden

Jack Chamoun:

  • Aufgabenverteilung: 0.5 Stunden
  • Dokumentation prüfen: 1.5 Stunden

3.2 Grundlagen

Um dieses Projekt erfolgreich umzusetzen, werden verschiedene Technologien aus den Bereichen Embedded Systems und Webentwicklung kombiniert. Die zentrale Idee besteht darin, Sensordaten mithilfe eines Mikrocontrollers zu erfassen und über moderne Schnittstellen im Netzwerk bereitzustellen. Im Folgenden werden die wichtigsten technischen Grundlagen vorgestellt, die diese Umsetzung ermöglichen.

ESP32

Der ESP32 ist ein kostengünstiger und leistungsfähiger Mikrocontroller mit integriertem WLAN und Bluetooth. Aufgrund seiner Netzwerkfähigkeit eignet er sich besonders für IoT-Anwendungen (Internet of Things). Er kann als eigenständiger Server oder Client betrieben werden und ermöglicht die direkte Kommunikation mit anderen Geräten im lokalen Netzwerk oder über das Internet.

BMP280-Sensor

Der BMP280 ist ein Präzisionssensor von Bosch, der Temperatur und atmosphärischen Druck messen kann. Er kommuniziert mit Mikrocontrollern über I²C oder SPI und wird häufig in Anwendungen zur Umweltüberwachung eingesetzt.

I²C-Protokoll

I²C (Inter-Integrated Circuit) ist ein Kommunikationsprotokoll, das zwei Leitungen - SDA (Daten) und SCL (Takt) - verwendet, um mehrere Geräte wie Sensoren und Mikrocontroller zu verbinden. Es ist wegen seiner Einfachheit und Effizienz für die Kommunikation über kurze Entfernungen weit verbreitet.

mDNS / .local

Multicast-DNS ermöglicht es Geräten in einem lokalen Netz, Hostnamen aufzulösen, ohne dass ein zentraler DNS-Server erforderlich ist. Dies erleichtert die Entwicklung und Geräteerkennung, insbesondere in dynamischen Netzwerkumgebungen.

Rust & Leptos

Rust ist eine Systemprogrammiersprache, die sich auf Sicherheit, Geschwindigkeit und Gleichzeitigkeit konzentriert, während Leptos ein Frontend-Framework ist, das reaktive Webentwicklung in Rust ermöglicht.

3.3 Projekt

Hier wird die konkrete Umsetzung des Projekts beschrieben – von der Firmware des Mikrocontrollers bis hin zur Integration in die bestehende Wetteranwendung. Der Fokus liegt dabei auf der Programmierung des ESP32 und der Einbindung in die Rust-basierte Web-App.

3.3.1 ESP32 Firmware

Die Firmware für den ESP32 wurde mithilfe der Arduino-Entwicklungsumgebung erstellt und nutzt mehrere spezialisierte Bibliotheken, um die Sensoransteuerung, Netzwerkkonnektivität und Datenbereitstellung über HTTP zu realisieren. Dadurch kann der Mikrocontroller die aktuelle Raumtemperatur erfassen und über eine REST-Schnittstelle im JSON-Format bereitstellen.

#include <Wire.h>             // I²C-Kommunikation mit dem Sensor
#include <WiFi.h>             // Verbindet ESP32 mit Wi-Fi
#include <Adafruit_BMP280.h>  // Schnittstellen mit dem BMP280 Sensor
#include <ArduinoJson.h>      // Zum Erstellen von JSON-Antworten
#include <WebServer.h>        // Erzeugt den lokalen HTTP-Server
#include <ESPmDNS.h>          // Ermöglicht die Auflösung von ‚.local‘ Hostnamen
#include "secrets.h"          // Speichert Wi-Fi-Anmeldedaten (nicht in der Versionskontrolle enthalten)

Sensor- und Kommunikationskonfiguration

Die I²C-Schnittstelle wird mit initialisiert:

Wire.begin(21, 22);

Dies bindet die I²C-Pins des ESP32: SDA an GPIO 21 und SCL an GPIO 22. Diese müssen mit der physikalischen Verdrahtung zwischen dem ESP32 und dem BMP280-Sensor übereinstimmen.

Um den BMP280-Sensor zu initialisieren, wird der folgende Code verwendet:

if (!bmp.begin(0x76)) {
    Serial.println("Could not find a valid BMP280 sensor, check wiring!");
    while (1);
}

Dabei wird versucht, mit dem Sensor an der Adresse 0x76 zu kommunizieren (ein üblicher Standardwert für BMP280). Wenn es nicht gefunden wird, hält das Programm an und gibt eine Fehlermeldung auf dem seriellen Monitor aus.

Initialisierung von Wi-Fi und Server

Der ESP32 verbindet sich mit dem konfigurierten Wi-Fi-Netzwerk:

WiFi.begin(WIFI_SSID, WIFI_PASSWORD);

Sobald die Verbindung hergestellt ist, registriert das Gerät einen lokalen Hostnamen mit mDNS, so dass es als roomtemperature.local aufgerufen werden kann:

MDNS.begin("roomtemperature");

Konfiguration des Webservers

Der HTTP-Server wird mit aktiviertem Cross-Origin Resource Sharing (CORS) eingerichtet, so dass er von verschiedenen Ursprüngen aus aufgerufen werden kann (z.B. von einer Web-App auf einem anderen Port):

server.enableCORS(true);

Es wird eine Route für den Pfad /temperature registriert, die den getTemperature-Handler aufruft:

server.on("/temperature", getTemperature);
server.begin();

Dadurch wird der Webserver gestartet, der nun auf eingehende HTTP-Anfragen wartet.

Behandlung von Temperaturanforderungen

Wenn eine Anfrage an /temperature gestellt wird, wird die folgende Funktion ausgeführt:

void getTemperature() {
  StaticJsonDocument<200> jsonDoc;
  jsonDoc["temperature"] = temperature;
  String response;
  serializeJson(jsonDoc, response);
  server.send(200, "application/json", response);
}

Diese Funktion liest die aktuelle Temperatur, formatiert sie in ein JSON-Objekt und gibt sie als Antwort mit dem Inhaltstyp application/json zurück.

Die Temperatur wird regelmäßig aus dem Sensor ausgelesen und in einer Variablen gespeichert:

temperature = bmp.readTemperature();

Dieser Wert wird in der Hauptschleife aktualisiert und auf Anforderung bereitgestellt.

3.3.2 Integration mit dem Wetter App

Die Raumtemperaturdaten des ESP32 werden nahtlos in das bestehende Wetterapp integriert, die mit dem Web-Framework Leptos in Rust entwickelt wurde. Dabei erfolgt die Kommunikation asynchron, um eine flüssige und nicht blockierende Benutzeroberfläche sicherzustellen.

Anfrage an ESP32-Webserver

Die Funktion fetch_esp32_data sendet eine HTTP-GET-Anfrage an die lokale Adresse http://roomtemperature.local/temperature. Die Antwort wird geprüft und im Erfolgsfall als JSON-Daten deserialisiert.

pub async fn fetch_esp32_data() -> Result<ESP32Data, Box<dyn Error>> {
    let ip_address = "roomtemperature.local";
    let url = format!("http://{ip_address}/temperature"); 

    let response = Request::get(&url).send().await?;
    if !response.ok() {
        return Err(format!("Error fetching data: {}", response.status()).into());
    }

    let data: ESP32Data = response.json().await?;
    Ok(data)
}

Deserialisierung der JSON-Antwort mit Serde

Die empfangenen JSON-Daten werden mithilfe von serde in eine Rust-Struktur überführt. Das Feld temperature ist optional, um mit möglichen fehlenden oder fehlerhaften Werten umgehen zu können.

#[derive(Deserialize, Debug, Clone)]
pub struct ESP32Data {
    pub temperature: Option<f64>,
}

Regelmäßige Abfrage im 10-Sekunden-Takt

Ein Signal trigger wird alle 10 Sekunden ausgelöst. Dadurch kann die Abfrage automatisch im Hintergrund stattfinden, ohne dass der Nutzer manuell aktualisieren muss.

let trigger = RwSignal::new(());

Effect::new(move || {
        let interval = Interval::new(10_000, move || {
            trigger.set(());   
        });
        interval.forget();
    });

Speicherung in einem Signal für die UI-Bindung

Das empfangene Temperaturdatum wird in einem weiteren Signal roomtemp gespeichert. Dieses Signal ist direkt mit der Benutzeroberfläche verbunden – sobald es aktualisiert wird, reagiert die UI automatisch darauf und zeigt den neuen Wert an.

let roomtemp = RwSignal::new(None);

Effect::new(move || {
        if let Some(Some(data)) = esp32_resource.get().as_deref() {
            roomtemp.set(data.clone().temperature);
        }
    });

Da Signale in Leptos reaktiv sind, wird die Anzeige automatisch alle 10 Sekunden aktualisiert, sobald neue Daten vom ESP32 eintreffen.

3.4 Fazit

Mit dem Aufbau eines ESP32-basierten Webservers zur Erfassung und Bereitstellung von Raumtemperaturdaten wurde erfolgreich eine Brücke zwischen Hardware und Webanwendung geschlagen. Durch die Einbindung in die bestehende Wetter-App konnte das System um eine lokale, physikalische Datenquelle erweitert werden. Die Integration veranschaulicht, wie eingebettete Systeme und moderne Webtechnologien sinnvoll zusammenarbeiten können, um ein interaktives und erweitertes Nutzungserlebnis zu schaffen. Das Projekt bildet damit eine solide Grundlage für zukünftige Erweiterungen, wie beispielsweise weitere Sensoren oder erweiterte Analysefunktionen.

3.5 Probleme

Ein praktisches Problem bei der Kommunikation mit dem ESP32 bestand darin, dass sich dessen IP-Adresse im lokalen Netzwerk nach jedem Neustart ändern konnte. Dies erschwerte das zuverlässige Senden von HTTP-Anfragen, da die aktuelle IP-Adresse jedes Mal neu ermittelt werden musste.

Um dieses Problem zu umgehen, wurde mDNS (Multicast DNS) verwendet. Dadurch ist der ESP32 über einen festen Hostnamen wie roomtemperature.local im Netzwerk erreichbar, unabhängig von seiner tatsächlichen IP-Adresse. So kann der Webserver stabil und dauerhaft über eine URL angesprochen werden, ohne manuelle Netzwerkkonfiguration.

3.6 Quellen

4. Handtracking

4.1 Einleitung

Handgesten sind eine mächtige und natürliche Form der Kommunikation, die häufig in menschlichen Interaktionen und zunehmend auch in Mensch-Computer-Schnittstellen verwendet wird. Dieses Projekt, Handtracking, nutzt eine Webcam und Computer-Vision-Techniken, um bestimmte Handzeichen wie das Peace Sign (✌️), den Mittelfinger (🖕) und den Rock On (🤘) zu erkennen. Die Anwendung verwendet Python und die MediaPipe-Bibliothek, um Handgesten in Echtzeit zu erkennen und zu interpretieren.

Aufgabenverteilung

Jessica Schach:

  • Erstellung erster Teil des Berichts (ca. 2h)
  • Nachvollziehung des Codes (ca. 1h)

Jack Chamoun:

  • Vorbereitung und Implementierung Hand-Tracking (ca. 20h)

Meryem Ünüvar:

  • Code-Refactoring mit Readme (ca. 3h)

Naqeebah Binti Mohd Zaini:

  • Erstellung zweiter Teil des Berichts (ca. 2h)
  • Nachvollziehung des Codes (ca. 1h)

4.2 Grundlagen

4.2.1 Hand-Tracking

Bei der Handverfolgung handelt es sich um eine Technik aus dem Bereich der Computer Vision, bei der die Bewegung und Position einer menschlichen Hand in einem Videobild erkannt und verfolgt wird. Eine genaue Handverfolgung ermöglicht die Erkennung von Gesten durch die Lokalisierung und Analyse von Orientierungspunkten an der Hand, wie z. B. Fingerspitzen, Gelenke und das Handgelenk.

In diesem Projekt wird das MediaPipe-Framework von Google für die Handverfolgung verwendet. MediaPipe bietet ein vorab trainiertes Modell, das 21 Orientierungspunkte pro Hand mit hoher Präzision und Echtzeitleistung erkennt.

Initialisierung im Code:

mp_hands = mp.solutions.hands
hands = mp_hands.Hands(
    static_image_mode=False,
    max_num_hands=2,
    min_detection_confidence=0.5,
    min_tracking_confidence=0.5
)

4.2.1 Python

Python wurde bereits in Kapitel 1.2.2 vorgestellt. Diese Programmiersprache konnte aufgrund der vielseitigen Einsatzbarkeit ebenfalls in diesem Projekt angewendet werden.

4.3 Projekt

4.3.1 Systemanforderungen

  • Python 3.x
  • OpenCV
  • MediaPipe
  • Standard-Gerätekamera (z. B. eingebaute Laptop-Webcam)

4.3.2 Dateistruktur

  • run.py - Hauptskript der Anwendung.
  • utils.py - Enthält Konfiguration, Hilfsfunktionen und Gestenerkennungslogik.
  • output.json - JSON-Datei zum Speichern von Protokollen der erkannten Gesten.

4.3.3 Gestenerkennungslogik

Das System erkennt drei Gesten, indem es die relative Position von Handmerkmalen analysiert.

Peace Sign (✌️)

Wird erkannt, wenn Zeige- und Mittelfinger gestreckt und alle anderen Finger geknickt sind:

def detect_peace_sign(landmarks):
    if (
        is_finger_up(landmarks[8], landmarks[5]) and
        is_finger_up(landmarks[12], landmarks[9]) and
        is_finger_down(landmarks[16], landmarks[13]) and
        is_finger_down(landmarks[20], landmarks[17])
    ):
        print("✌️ Peace sign detected!")
        return True
    return False

Rock Sign (🤘)

Wird erkannt, wenn der Zeige- und der kleine Finger ausgestreckt und der Mittel- und der Ringfinger zusammengeklappt sind:

def detect_rock_sign(landmarks, log):
    if (
        is_finger_up(landmarks[8], landmarks[5]) and
        is_finger_up(landmarks[20], landmarks[17]) and
        is_finger_down(landmarks[12], landmarks[9]) and
        is_finger_down(landmarks[16], landmarks[13])
    ):
        msg = f"🤘 Rock on! Detected at {get_current_time()}"
        print(msg)
        log.append(msg)

Middle Finger Gesture (🖕)

Wird erkannt, wenn nur der Mittelfinger ausgestreckt wird, was oft als unethische Geste interpretiert wird. Es wird eine Warnung protokolliert:

msg = f"⚠️ Warning: non-ethical sign detected at {get_current_time()}"
print(msg)
log.append(msg)

4.3.4 Ablauf des Programms

In diesem Abschnitt wird der Ablauf des Handtracking-Systems skizziert, begleitet von anschaulichen Screenshots zu den wichtigsten Phasen des Programms.

Schritt 1: Initialisierung

Das Programm beginnt mit dem Zugriff auf die Gerätekamera und der Konfiguration von Auflösung und Bildrate.

cap = cv2.VideoCapture(0)
cap.set(cv2.CAP_PROP_FRAME_WIDTH, FRAME_WIDTH)
cap.set(cv2.CAP_PROP_FRAME_HEIGHT, FRAME_HEIGHT)

Schritt 2: Frame-Erfassung und Hand-Erkennung

Jedes Bild der Webcam wird erfasst und von BGR in RGB umgewandelt. Das MediaPipe-Modell verarbeitet das Bild und gibt die Koordinaten der Handmarkierung zurück, wenn eine Hand erkannt wird.

image_rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
results = hands.process(image_rgb)

Live-Videoübertragung mit auf der Hand eingezeichneten Orientierungspunkten und Verbindungslinien.

Schritt 3: Erkennung von Gesten

Wenn Landmarken verfügbar sind, prüft das System anhand bestimmter geometrischer Bedingungen auf bekannte Handgesten:

detect_middle_finger(landmarks, instructions_log)
detect_rock_sign(landmarks, instructions_log)
detect_peace_sign(landmarks)

Erkennung des Peace-Zeichens mit einer Meldung auf dem Terminal (z. B. “✌️ Peace sign detected!”) Rock-on-Geste mit Protokollierungsausgabe (z. B. „🤘 Rock on! Detected at…“)

Schritt 4: Anzeige und Feedback

Landmarken werden mit cv2 auf das Bild gezeichnet, und der kommentierte Videostrom wird in Echtzeit angezeigt. Der Benutzer kann das Gestenfeedback sowohl im Videofenster als auch auf dem Terminal sehen.

cv2.imshow("🖐 Hand Tracking", frame)

Schritt 5: Logging und Exit

Erkannte Gesten, insbesondere Warnungen und Sonderzeichen, werden an eine JSON-Logdatei angehängt:

with open(OUTPUT_FILE, "w") as f:
    json.dump(instructions_log, f, indent=2)

Die Anwendung wird ordnungsgemäß beendet, wenn der Benutzer die Taste q drückt.

4.4 Fazit

Das Handtracking-Projekt demonstriert erfolgreich die Gestenerkennung in Echtzeit, wobei nur eine Laptop-Kamera und Open-Source-Tools verwendet werden. Durch die Verwendung der robusten Handmarkenerkennung von MediaPipe und die Kombination mit Python und OpenCV ist das System in der Lage, spezifische Handgesten mit Genauigkeit und Geschwindigkeit zu identifizieren.

Die modulare Struktur des Codes ermöglicht eine einfache Wartung und potenzielle Erweiterung, während die Protokollierung der erkannten Gesten nützliches Feedback liefert. Obwohl die derzeitige Implementierung nur eine begrenzte Anzahl statischer Gesten unterstützt, stellt sie eine solide Grundlage für komplexere Interaktionssysteme dar.

Insgesamt unterstreicht dieses Projekt, wie zugängliche Technologien genutzt werden können, um intuitive und interaktive Computer-Vision-Anwendungen zu entwickeln.

4.5 Probleme

Eine der größten Herausforderungen in diesem Projekt war die anfängliche Lernkurve im Zusammenhang mit Schlüsseltechnologien wie TensorFlow, maschinellen Lernkonzepten und MediaPipe. Während MediaPipe eine High-Level-API bietet, erforderte das Verständnis, wie es maschinelle Lernmodelle für die Echtzeit-Erkennung von Landmarken einsetzt, Hintergrundwissen in Computer Vision. Auch das Verständnis, wie TensorFlow vielen modernen Bildverarbeitungspipelines zugrunde liegt, machte die Sache noch komplexer, vor allem für diejenigen, die neu auf dem Gebiet sind. Die Überwindung dieser Hürden war entscheidend für die erfolgreiche Umsetzung der Gestenerkennung im Projekt.

4.6 Quellen

5. Docker

5.1 Einleitung

Die in Kapitel 2 entwickelte WetterApp wurde in der Programmiersprache Rust umgesetzt. Diese moderne und leistungsfähige Sprache bringt jedoch eine gewisse Einstiegshürde mit sich – insbesondere unter Windows. Die Einrichtung und Ausführung von Rust-Projekten gestaltet sich dort oft umständlich, was in unserem Fall die Verwendung von WSL (Windows Subsystem for Linux) oder einer virtuellen Maschine erforderlich machte.

Mit Docker lässt sich dieses Problem elegant lösen: Docker ermöglicht es, Anwendungen in sogenannten Containern bereitzustellen. Ein Container enthält das vollständige Laufzeit- und Abhängigkeitsumfeld eines Projekts – unabhängig vom Betriebssystem oder der lokalen Konfiguration des Zielrechners. Dadurch kann die WetterApp nun einfach und plattformunabhängig ausgeführt werden – ohne separate Rust-Installation oder manuelle Einrichtungsschritte.

Ein weiterer Anwendungsfall für Docker in diesem Projektkontext ist die geplante Auslagerung des Hand-Tracking-Systems aus Kapitel 4 auf einen Raspberry Pi. Durch den Einsatz eines Containers kann auch dieses System problemlos und konsistent auf der Hardware betrieben werden, ohne Anpassungen am Code oder an der Umgebung vornehmen zu müssen.

Aufgabenverteilung

Meryem Ünüvar:

  • Docker-Integration + Recherche: 3 Stunden

Jessica Schach:

  • Recherche: 1 Stunde
  • Berichtverfassung Teil Leptos: 3 Stunden
  • Berichtverfassung Teil Handtracking: 3 Stunden

Naqeebah Zaini:

  • Berichtanpassung: 2 Stunden

Jack Chamoun:

  • Anpassung README.md
  • Prüfung des Berichts
  • Gesamt: 1 Stunde

5.2 Grundlagen

5.2.1 Dockerfile

Ein Dockerfile ist eine Art Bauanleitung für einen Docker-Container. Es beschreibt Schritt für Schritt, wie ein Container aufgebaut wird, welche Programme installiert und welche Konfigurationen vorgenommen werden müssen. Beim Erstellen eines Docker-Images liest Docker dieses File und führt die Anweisungen in der angegebenen Reihenfolge aus.

In diesem Kapitel werden die wichtigsten Bestandteile eines Dockerfiles erläutert.

Docker-Image (FROM)

Jedes Dockerfile beginnt mit der Definition eines Basis-Images. Dieses legt fest, auf welchem Betriebssystem oder mit welchen vorinstallierten Komponenten der Container aufbaut.

Arbeitsverzeichnis (WORKDIR)

Zudem ist es notwendig im Dockerfile das Arbeitsverzeichnis anzugeben, auf das sich die Befehle beziehen sollen.

Dateien kopieren (COPY)

Der COPY-Befehl wird verwendet, um Dateien vom lokalen Projektverzeichnis in den Container zu kopieren.

Programme ausführen (`RUN)

Der RUN-Befehl führt während der Erstellung des Docker-Images Shell-Befehle aus – z. B. zur Installation von Programmen oder zum Einrichten von Umgebungen.

Port (EXPOSE)

Mit EXPOSE wird ein Port angegeben, über den der Container mit der Außenwelt kommunizieren kann. Dieser Befehl ist rein informativ – er hat keine sicherheitsrelevante Wirkung, signalisiert jedoch, dass der Container beispielsweise einen Webserver auf einem bestimmten Port bereitstellt.

Startbefehl (CMD)

Der Command-Befehl CMD legt die Standardbefehle fest, die bei Start des Docker-Containers ausgeführt werden.

5.2.2 Dockerignore

Die Datei .dockerignore ist ähnlich wie die .gitignore. Der Unterschied liegt lediglich darin, dass die .dockerignore für Docker statt für Git festlegt, welche Dateien und Ordner ignoriert werden sollen, wenn das Image mit docker build erstellt wird.

5.2.3 Raspberry Pi

Der Raspberry Pi ist ein kredit­kartengroßer Ein-Platinen-Computer (Single-Board Computer, SBC), entwickelt von der Raspberry Pi Foundation, um niederschwelligen Zugang zu Computer­wissen zu schaffen.

Das erste Modell B wurde am 29. Februar 2012 veröffentlicht; binnen weniger Monate entstanden weltweit Bildungsprojekte und Bastel-Blogs, die den Pi als günstige Linux-Plattform propagierten.

Heute zählen mehr als 60 Millionen verkaufte Boards. Einsatzfelder reichen von IoT-Gateways (Smart-Home-Hubs, Wetterstationen, Security-Kameras) über industrielle Steuerungen bis zu Edge-KI-Prototypen.

5.3 Projekt: Dockerisierung des Leptos-Projekts

5.3.1 Dockerfile

FROM rust:1.87                                              #(1)

WORKDIR /app                                                #(2)

COPY . .                                                    #(3)

RUN rustup target add wasm32-unknown-unknown && \           #(4)
cargo install --locked trunk                                #(5)

EXPOSE 8080                                                 #(6)

CMD ["trunk", "serve", "--address", "0.0.0.0"]              #(7)
  1. Docker-Image: Rust Version 1.87
  2. Arbeitsverzeichnis im Container auf /app
  3. Der COPY-Befehl kopiert alle Dateien aus dem aktuellen Verzeichnis (wo die Dockerfile liegt) in das Arbeitsverzeichnis /app im Container
  4. Festlegung des Kompilierungsziels für WebAssembly
  5. Installation des Tools Trunk
  6. Port: 8080
  7. trunk serve: Start des lokalen Webservers; --adress 0.0.0.0 Anbinung an Netzwerkschnittstelle

5.3.2 Dockerignore

*.md

In der .dockerignore werden alle Markdown-Dateien bei Erstellung des Docker-Images ignoriert.

5.3.3 Ausführung

Die Ausführung der WetterApp im Container wird ebenfalls in der README.md genauer erklärt.

Bevor man den Docker-Container ausführen kann wird eine Installation von Docker vorausgesetzt. Wenn Docker installiert ist, sind folgende Befehle auszuführen:

cd docker/weatherapp 

docker build --no-cache -t wetterapp .

docker run -p 8080:8080 wetterapp:latest

Nach einer etwas längeren Wartezeit von docker build und docer run erscheint folgende Ausgabe:

Finished `dev` profile [unoptimized + debuginfo] target(s) in 1m 03s
2025-06-01T10:14:15.713726Z  INFO downloading wasm-bindgen version="0.2.100"
2025-06-01T10:14:17.112614Z  INFO installing wasm-bindgen
2025-06-01T10:14:18.350277Z  INFO applying new distribution
2025-06-01T10:14:18.351657Z  INFO success
2025-06-01T10:14:18.353489Z  INFO serving static assets at -> /
2025-06-01T10:14:18.354549Z  INFO server listening at:
2025-06-01T10:14:18.355003Z  INFO     http://127.0.0.1:8080/
2025-06-01T10:14:18.355025Z  INFO     http://172.17.0.2:8080/
2025-06-01T10:14:18.357703Z  INFO     http://localhost.:8080/
2025-06-01T10:14:18.357765Z  INFO     http://4ef5cea1bc91.:8080/

Über die Links kann dann die WetterApp geöffnet werden.

Um die aktiven Container anzeigen zu lassen, kann der Befehl docker ps -a ausgeführt werden.

5.3.4 Probleme

Die Erweiterung des Leptos-Projekts ging mit mehreren technischen Herausforderungen einher.

Zum einen erwies sich die offizielle Dokumentation von Trunk als unvollständig. Insbesondere fehlte der Hinweis, dass das Tool wasm-bindgen-cli erforderlich ist, sofern kein M1-Prozessor verwendet wird. Diese Informationslücke führte zunächst dazu, dass der Webserver nicht erfolgreich gestartet werden konnte.

Nachdem dieses Problem behoben war, stellte die Konfiguration der Portweiterleitung eine weitere Hürde dar. Es wurde zunächst angenommen, dass die Angabe des Ports 8080 im EXPOSE-Statement des Dockerfiles ausreichend sei. Tatsächlich wird der Port jedoch vom Docker-Daemon nur dann nach außen freigegeben, wenn beim Start des Containers zusätzlich die Portweiterleitung explizit mittels -p im docker run-Befehl konfiguriert wird.

5.4 Projekt: Dockerisierung des Hand-Tracking-Projekts

In diesem Kapitel wird die Auslagerung des Hand-Tracking-Projekts auf einen Raspberry Pi mit Hilfe eines Docker-Containers beschrieben.

5.4.1 Dockerfile

# Use a slim Python base image
FROM python:3.11-slim

# Install system dependencies needed for OpenCV GUI support
RUN apt-get update && apt-get install -y
RUN apt install libgl1 -y
RUN apt install libglib2.0-0 -y
RUN apt install libsm6 -y
RUN apt install libxext6 -y
RUN apt install libxrender1 -y
RUN apt install libgl1 -y
RUN rm -rf /var/lib/apt/lists/*

# Set the working directory
WORKDIR /app
# Setting the Environment because mediapipe will not be installed on Raspberrypi OS lite 64 bite without the venv
RUN python3 -m venv /venv
RUN /venv/bin/pip install --upgrade pip
RUN /venv/bin/pip install opencv-python
RUN /venv/bin/pip install mediapipe
COPY application/ .

# Run the script 
CMD ["/venv/bin/python", "run_heedless.py"]

5.4.2 Dockerignore

env/

In der .dockerignore ist festgelegt, dass alle Dateien im env-Ordner ausgeschlossen werden.

5.4.3 Ausführung

Um das Handtracking-Projekt auf einem Raspberry Pi ausführen zu können, wurde folgende Hardware verwendet:

  • Raspberry Pi Zero 2
  • Rapoo Camera
  • MicroSD Card (at least 8 GB)
  • USB Card Reader
  • USB Power-Adapter
  • Micro USB OTG Cable (For connection between camera and Raspberry Pi)

Der Raspberry Pi muss über SSH verbunden werden. Die genaue Anleitung befindet sich in der README.md im Projekt HandTracking.

Nachdem alles angeschlossen ist und der Raspberry Pi über SSH verbunden ist, kann die Erkennung der Kamera mit folgendem Befehl getestet werden:

ls /dev/video0

Im nächsten Schritt kann dann das Programm via Docker gestartet werden. Hierfür sind folgende Befehle über SSH auszuführen:

docker run -it --device=/dev/video0   

Um die aktiven Container anzeigen zu lassen, kann der Befehl docker ps -a ausgeführt werden.

Folgendes Bild zeigt die Nutzung des Hand-Tracking Applikation mit Hilfe eines Raspberry Pis in einem Terminal als Screenshot.

Raspberry Pi Demonstration

5.4.4 Probleme

Die Integration des Hand-Tracking-Projekts auf einem Raspberry Pi gestaltete sich komplexer als zunächst angenommen und erforderte verschiedene Optimierungsmaßnahmen zur Reduktion der Systemlast. Um die begrenzten Ressourcen des Raspberry Pi effizient zu nutzen, musste die Auflösung der Kamera bewusst reduziert werden. Diese Maßnahme führte jedoch zu einer merklichen Verschlechterung der Bildqualität, was sich negativ auf die Genauigkeit und Stabilität der Handerkennung auswirkte.

Darüber hinaus war es erforderlich, das System im sogenannten Headless-Modus zu betreiben – also ohne grafische Benutzeroberfläche. Dies ermöglichte eine ressourcenschonendere Ausführung, erforderte jedoch auch eine verstärkte Nutzung von Kommandozeilen-Tools.

Die Steuerung des Raspberry Pi erfolgte über eine SSH-Verbindung, was grundlegende Kenntnisse im Umgang mit Linux-Kommandozeilen voraussetzte. Insbesondere Befehle wie ls, cd, mkdir, rm und dir waren essenziell, um Dateisysteme zu navigieren, Verzeichnisse zu erstellen oder zu verwalten und notwendige Konfigurationen vorzunehmen.

5.5 Fazit

Die Integration von Docker in das Projekt erwies sich als äußerst wirkungsvoll, um plattformunabhängige und reproduzierbare Entwicklungs- und Ausführungsumgebungen bereitzustellen. Insbesondere bei der Arbeit mit Rust – einer leistungsfähigen, aber teilweise komplexen Sprache mit anspruchsvoller Toolchain – zeigte sich Docker als effektive Lösung zur Vereinfachung des Setups, insbesondere unter Windows.

Durch die Containerisierung der WetterApp konnte diese ohne zusätzliche Konfiguration oder lokale Abhängigkeiten auf verschiedenen Systemen betrieben werden. Auch die geplante Auslagerung des Hand-Tracking-Projekts auf einen Raspberry Pi profitierte erheblich von Docker: Die Ausführung in einem abgeschlossenen Container erleichtert nicht nur die Portierung auf andere Geräte, sondern reduziert zudem potenzielle Fehlerquellen durch inkonsistente Umgebungen.

Trotz einzelner Herausforderungen – wie der unvollständigen Trunk-Dokumentation und der notwendigen manuellen Portweiterleitung – konnte die Dockerisierung erfolgreich umgesetzt werden. Die gewonnenen Erkenntnisse unterstreichen die Relevanz containerisierter Architekturen für moderne Softwareprojekte, insbesondere im Bereich der plattformübergreifenden Anwendungsentwicklung.

5.6 Quellen

6. CI/CD - Pipelines

6.1 Einleitung

Bei modernen Softwareprojekten ist es unerlässlich, Änderungen am Code automatisiert zu testen, zu bauen und bereitzustellen. Continuous Integration (CI) und Continuous Deployment (CD) spielen dabei eine zentrale Rolle, da sie Entwicklungsprozesse effizienter, transparenter und weniger fehleranfällig machen.

In unserem Leptos-Projekt aus Kapitel 2 kommt eine CI/CD-Pipeline auf Basis von GitLab zum Einsatz. Sie sorgt dafür, dass neue Commits automatisch überprüft, notwendige Abhängigkeiten installiert, die Anwendung mithilfe von Trunk gebaut und auf mögliche Fehler untersucht wird – und das bei jedem Push ins Repository. Auf diese Weise wird ein stabiler, reproduzierbarer und teamfreundlicher Entwicklungsprozess gewährleistet.

Ein besonderer Fokus liegt zudem auf dem automatisierten Deployment: Die gebaute WebAssembly-Anwendung wird im Rahmen der Pipeline direkt auf GitHub Pages veröffentlicht. Damit wird der CD-Ansatz konsequent umgesetzt und die Anwendung nach jedem erfolgreichen Build sofort öffentlich bereitgestellt.

Aufgabenverteilung

Meryem Ünüvar:

  • Erstellung + Konfiguration GitHub-Repository
  • CD-Teil in .yaml-Datei
  • Testen der Funktionlität
  • Aufsetzen GitLab Runner
  • Gesamtaufwand: 12 Stunden

Jessica Schach:

  • Recherche
  • Berichtverfassung
  • Gesamtaufwand: 5 Stunden

Naqeebah Zaini:

  • CI-Teil in .yaml-Datei
  • Testen der Runner, Pipeline
  • Viele Anpassungen
  • Gesamtaufwand: 6 Stunden

Jack Chamoun:

  • Prüfung von Bericht
  • Prüfung von .yaml-Datei
  • Test des Endergebnisses
  • Gesamtaufwand: 3 Stunden

6.2 Grundlagen

6.2.1 Continuous Integration (CI)

Continuous Integration bezeichnet das automatisierte Zusammenführen und Testen von Codeänderungen in einem zentralen Repository. Ziel ist es, Integrationsprobleme frühzeitig zu erkennen und den Code stets in einem funktionsfähigen Zustand zu halten. Bei jedem Commit oder Merge-Request wird ein definierter Ablauf (Pipeline) angestoßen, der den Code überprüft, testet und baut.

In unserem Fall wird bei jedem Push ins GitLab-Repository automatisch geprüft, ob sich die Leptos-Anwendung noch erfolgreich kompilieren lässt.

6.2.2 Continuous Deployment (CD)

Continuous Deployment geht einen Schritt weiter: Nach erfolgreichem Build und Tests wird die Anwendung automatisch auf eine Zielumgebung ausgerollt – z. B. auf einen Webserver. Die manuelle Freigabe entfällt dabei.

Für unser Projekt wäre ein mögliches Ziel das automatische Bereitstellen der WebAssembly-basierten Website auf einem Webserver oder einem Hosting-Dienst wie GitLab Pages.

6.2.3 GitLab CI/CD

GitLab bietet eine integrierte CI/CD-Lösung. Die Abläufe werden über eine Datei namens .gitlab-ci.yml im Projektverzeichnis definiert. In dieser Datei werden sogenannte Jobs und Stages festgelegt. Jobs beinhalten konkrete Anweisungen, z. B. „Rust und Trunk installieren“ oder „App kompilieren“, und werden abhängig voneinander in Phasen (Stages) organisiert, z. B. build, test, deploy.

Die GitLab Runner führen diese Jobs in isolierten Umgebungen aus, entweder in Containern oder auf dedizierten Maschinen. In unserem Fall nutzen wir einen Docker-Runner mit einer passenden Rust-Umgebung.

6.2.5 GitHub-Pages

GitHub Pages ist ein kostenloser Hosting-Dienst von GitHub, der es ermöglicht, statische Webseiten direkt aus einem Git-Repository heraus zu veröffentlichen. Sobald ein entsprechender Branch – meist main oder gh-pages – eingerichtet und mit den benötigten Webinhalten befüllt wurde, stellt GitHub die Seite automatisch unter einer öffentlich zugänglichen URL zur Verfügung.

Dieser Dienst eignet sich insbesondere für Projekte, die aus statischen Inhalten bestehen, wie beispielsweise HTML-, CSS- und JavaScript-Dateien oder in unserem Fall WebAssembly-Anwendungen, die aus Rust mittels Trunk generiert wurden.

Die Aktivierung von GitHub Pages erfolgt in den Repository-Einstellungen, wobei festgelegt werden kann, welcher Branch und gegebenenfalls welcher Ordner (z. B. /docs oder /dist) als Quelle dienen soll. GitHub übernimmt dann das automatische Bereitstellen der Inhalte über seine Infrastruktur, ohne dass ein eigener Webserver erforderlich ist.

Im Kontext unseres Projekts stellt GitHub Pages somit eine einfache und effiziente Möglichkeit dar, das Ergebnis der CI/CD-Pipeline – also die gebaute Leptos-Webanwendung – direkt öffentlich zugänglich zu machen. In Kombination mit automatisierten Deployments aus GitLab heraus kann dadurch ein kontinuierlicher Veröffentlichungsprozess realisiert werden.

6.3 Projekt

Ziel des Projekts ist, eine Pipeline für die Leptos WetterApp aus Kapitel 2 zu erstellen. Diese Pipeline soll zum Einen Code-Änderungen im zentralen Repository testen (Continuous Integration). Zum Anderen soll die WetterApp auf GithubPages deployed werden mit Hilfe von Continuous Deployment.

Die GitLab-CI Pipeline sowie der CD-Teil für das Leptos-Projekt sind in einer .gitlab-ci.yml Datei definiert.

6.3.1 CI/CD-Konfigurationsdatei

Die .gitlab-ci.yml-Datei bildet das zentrale Steuerungselement für den CI/CD-Prozess in GitLab. Sie definiert, wie und wann Build- und Deployment-Prozesse ausgelöst werden, auf welchen Umgebungen sie laufen, und welche Abhängigkeiten erforderlich sind.

6.3.1.1 Continuous Integration (CI)

image: "rust:latest"                                                        # (1)                

workflow:                                                                   # (2)
  rules:
    - if: '$CI_PIPELINE_SOURCE == "merge_request_event" 
        && $CI_MERGE_REQUEST_TARGET_BRANCH_NAME == "main"'

stages:                                                                     # (3)
  - build

variables:                                                                  # (4)
  CARGO_TERM_COLOR: always

build:                                                                      # (5)
  stage: build
  tags:
    - leptos
  before_script:
    - rm -rf weatherapp/target
    - apt-get update && apt-get install -y libssl-dev pkg-config curl
    - rustup target add wasm32-unknown-unknown
    - cargo install trunk
  script:
    - cd weatherapp
    - trunk build --release
  artifacts:
    paths:
      - weatherapp/dist/
  rules:
    - changes:
        - weatherapp/**/*
      when: always
(1) Image

In der yml-Datei wurde als Basis-Image das offizielle Rust-Docker-Image rust:latest verwendet.

(2) Workflow

Der Workflow ist so konfiguriert, dass die Pipeline nur bei Merge-Requests ausgeführt wird, wenn das Ziel-Branch der Haupt-Branch (main) ist. Dadurch wird sichergestellt, dass nur relevante Änderungen den Build-Prozess anstoßen.

(3) Stages

Die Pipeline besteht aus einer einzigen Stage namens build. Diese führt den Build-Prozess der Leptos-App durch.

(4) Variablen

Die Umgebungsvariable CARGO_TERM_COLOR ist auf always gesetzt, um farbige Ausgabe im Build-Log zu ermöglichen.

(5) Build

Der build Job läuft auf einem GitLab-Runner mit dem Tag leptos.

Vor dem eigentlichen Build werden mit before_script notwendige Vorbereitungen getroffen: - Entfernen des alten target-Ordners zur Vermeidung von Konflikten. - Installation von Systemabhängigkeiten (libssl-dev, pkg-config, curl), die für Rust-Builds notwendig sind. - Hinzufügen des WebAssembly-Ziels wasm32-unknown-unknown. - Installation von trunk, dem Build-Tool für Leptos.

Im script-Abschnitt wird in das Projektverzeichnis weatherapp gewechselt und die Anwendung mit trunk build –release kompiliert.

Die gebauten Dateien im Ordner weatherapp/dist/ werden als Artefakte gespeichert und sind so für weitere Pipeline-Schritte oder zum Download verfügbar. Dies ist unter artifacts definiert.

Der Abschnitt rules beschreibt, dass der Build-Job immer dann ausgeführt wird, wenn sich Dateien im Ordner weatherapp/ geändert haben.

6.3.1.2 Continuous Deployment (CD)

deploy_to_github:                                                           # (1)
  stage: deploy                                                             # (2)
  tags:                                                                     # (3)
    - leptos
  image: ubuntu:latest                                                      # (4)
  before_script:                                                            # (5)
    - apt-get update && apt-get install -y git
  script:                                                                   # (6)
    - git config --global user.email "gruppe05@mail.com"
    - git config --global user.name "Gruppe05"
    - cd weatherapp/
    - git clone https://meryem9907:${GH_TOKEN}@github.com/meryem9907/weatherapp.git
    - git remote set-url origin https://meryem9907:${GH_TOKEN}@github.com/meryem9907/weatherapp.git
    - rm -rf weatherapp/*
    - mv -f dist/* weatherapp/
    - cd weatherapp
    - git checkout -B pages origin/main
    - git add .
    - | 
      if git diff --cached --quiet; then 
        echo "No changes to commit"
      else
        git commit -m "Deploy weatherapp from GitLab CI"
        git push origin pages:main
      fi
  dependencies:                                                              # (7)
    - build
  rules:                                                                     # (8)
    - changes:
        - weatherapp/**/*
    - when: always
(1) Job-Definition

Der Job deploy_to_github ist für das automatische Deployment der gebauten Anwendung auf GitHub zuständig. Er wird nach dem erfolgreichen Build ausgeführt.

(2) Stage

Der Job ist Teil der Stage deploy, die nach der Stage build ausgeführt wird. Dies stellt sicher, dass nur erfolgreich gebaute Artefakte deployed werden.

(3) Tags

Wie beim Build-Job wird der CD-Job auf einem Runner mit dem Tag leptos ausgeführt. Dies ermöglicht die gezielte Zuweisung zu einem für Leptos-Projekte vorbereiteten Runner.

(4) Image

Im Gegensatz zum Build-Job verwendet dieser Job das Basis-Image ubuntu:latest. Dies ist notwendig, da Git benötigt wird, um die Verbindung zu GitHub herzustellen und Repository-Operationen auszuführen. Git ist im Rust-Image nicht standardmäßig enthalten.

(5) before_script

Vor der Ausführung des Deployments wird folgendes Werkzeug installiert: apt-get update && apt-get install -y git: Installiert Git, das für das Klonen und Pushen auf GitHub erforderlich ist.

(6) Skript

Der Deployment-Prozess besteht aus mehreren Schritten:

1. git config --global ...:

  • Setzt globale Git-Konfigurationswerte wie Benutzername und E-Mail für den Commit.
  • Initialisiert Git auf einer neuen Umgebung.

2. cd weatherapp/:

  • Wechselt in das Projektverzeichnis weatherapp.

3. GitHub-Zugriff:

  • git clone ...: Klont das Ziel-Repository mit einem Personal Access Token (${GH_TOKEN}) zur Authentifizierung.
  • git remote ...: Aktualisiert die Remote-URL, um zukünftige Push-Vorgänge ebenfalls authentifiziert (mit personal access token) durchzuführen.

4. Dateiübertragung:

  • rm -rf weatherapp/*: Löscht den Inhalt des vorhandenen weatherapp/-Verzeichnisses (um veraltete Dateien zu entfernen).
  • mv -f dist/* weatherapp/: Verschiebt den Inhalt aus dem dist/-Ordner (build result) in das weatherapp/-Verzeichnis, das anschließend committed wird.

5. Branch-Management:

  • git checkout -B ...: Checkt den Branch pages (von origin/main) aus und überschreibt ihn ggf.
  • git add .: Fügt alle Änderungen zur Staging-Area hinzu.

6. Commit & Push:

  • if git diff ...: Prüft, ob es Änderungen gibt. Wenn keine Änderungen vorhanden sind, wird der Vorgang abgebrochen.
  • Bei Änderungen: Commit mit passender Nachricht und Push auf GitHub (pages:main), um GitHub Pages zu aktualisieren.
(7) Abhängigkeiten

Dieser Job hängt explizit vom vorherigen build-Job ab. Dadurch wird sichergestellt, dass die Artefakte des Builds (z. B. dist/) verfügbar sind, bevor das Deployment beginnt.

(8) Regeln

Die Regeln definieren, wann dieser Job ausgelöst wird. Zum Einen, wird er ausgelöst sobald sich Dateien im weatherapp/-Verzeichnis ändern. Zum Anderen sorft das Setzen von when: always dafür, dass der Job auch bei sonstigen Pipeline-Läufen durchläuft, sodass ein Deployment zu jeder Zeit möglich ist. Andernfalls müssen Event-Typen wie Merges den Job triggern.

Der Job wird ausgeführt, wenn sich Dateien im weatherapp/-Verzeichnis ändern.

Zusätzlich ist when: always gesetzt, was sicherstellt, dass der Job auch bei sonstigen Pipeline-Läufen durchläuft. Damit ist ein Deployment jederzeit möglich, nicht nur bei bestimmten Event-Typen wie Merges.

6.3.2 Start der Pipeline

Um die CI/CD-Pipeline überhaupt ausführen zu können, wird ein sogenannter GitLab Runner benötigt. Dieser Runner ist ein Dienst, der die im Repository definierten Jobs (z. B. Build, Test, Deployment) ausführt. Im aktuellen Projekt wurde ein lokaler Runner eingerichtet, der auf einem Gerät mit installierter Docker- und WSL-Umgebung läuft.

Für den Start der Pipeline müssen zunächst folgende Schritte durchgeführt werden:

  1. Runner in GitLab registrieren:
    • Im Projekt unter Settings > CI/CD > Runners den Button “Create project runner” auswählen.
    • Ein Tag für den Runner vergeben (z. B. leptos-runner) und das Betriebssystem Linux auswählen.
  2. Runner lokal in Docker starten (unter WSL):
    • Folgender Befehl startet einen neuen Runner-Container:

      docker run -d --name gitlab-runner --restart always \
        -v /srv/gitlab-runner/config:/etc/gitlab-runner \
        -v /var/run/docker.sock:/var/run/docker.sock \
        gitlab/gitlab-runner:latest
  3. Runner mit GitLab verbinden:
    • Den von GitLab generierten Befehl zur Registrierung des Runners ausführen, z. B.:

      docker exec -it gitlab-runner gitlab-runner register
    • Dabei werden die URL und ein Token benötigt, die im Registrierungsdialog in GitLab bereitgestellt werden.

  4. Runner starten:
    • Nach erfolgreicher Registrierung kann der Runner mit folgendem Befehl gestartet werden:

      docker exec -it gitlab-runner gitlab-runner run

Wichtig: Die in der .gitlab-ci.yml definierten tags müssen mit denen des registrierten Runners übereinstimmen, sonst wird die Pipeline nicht gestartet.

Die Pipeline wird ausgelöst bei jedem Merge Request oder bei Änderungen im Ordner weatherapp. Für Testzwecke kann z. B. die Datei test.txt im weatherapp-Verzeichnis angepasst werden.

Der vollständige Ablauf der Pipeline – inklusive Installation von Abhängigkeiten, Kompilierung mit trunk und Deployment – dauert je nach Systemkonfiguration ca. 20–30 Minuten. Das liegt unter anderem an der Installation und Einrichtung von trunk, das als Build-Werkzeug für Leptos-Anwendungen eingesetzt wird.

Damit ist die technische Grundlage für eine funktionierende CI/CD-Pipeline gelegt, die automatisiert auf Veränderungen im Code reagiert und die Anwendung entsprechend neu baut und deployt.

6.3.3 Deployment auf GitHub-Pages

Um die WetterApp auf GitHub-Pages deployen zu können, ist mehr Arbeit notwendig, als einfach die .gitlab-ci.yml zu erweitern.

Nicht zu vergessen ist die Konfiguration eines relativen Pfades in der Trunk.toml im WetterApp-Projekt. Fehlt diese Angabe, wird nur eine leere Seite angezeigt.

[build]
public_url = "./"

Die Aufgabe erfordert zudem einen GitHub-Account sowie ein Personal Access Token für die Berechtigungen, um auf GitHub-Pages pushen zu können. Dieser Token ist auch auf GitLab gespeichert (CI/CD-Settings -> Variables “GH_TOKEN”).

Als nächsten Schritt ist ein Repository für die WetterApp vorausgesetzt. Dieses findet sich unter dieser URL: https://github.com/meryem9907/weatherapp.

Bei den Repository-Einstellungen unter Pages wurde der Deployment-Branch auf main mit Root-Ordner / eingestellt. Hier soll der Inhalt des /dist-Verzeichnisses aus der WetterApp gespeichert werden. GitHub-Pages sucht dort nach einer index.html, führt diese aus und baut so eine Webseite.

Diese Webseite ist daher jetzt unter https://meryem9907.github.io/weatherapp/ erreichbar.

6.3.4 Ergebnis

Das Ergebnis des Projekts wurde bereits angeschnitten. Hier wird es nochmal zusammengefasst.

Es wurde erfolgreich eine Pipeline für das Deployment von der Leptos WetterApp eingerichtet. Die Ausführung der Pipelines erfolgen nacheinander und wird im folgenden Screenshot dargestellt.

Pipeline

Der nächste erreichte Meilenstein ist das Deployment von der Leptos WetterApp auf GitHub-Pages. Das folgende Foto zeigt das Repository und die vorhandene index.hmtl.

Deployment

In GitHub ist auch einsehbar, dass ein Deployment 19 Minuten vor der Bildaufnahme stattgefunden hat.

CD

Nun ist die WetterApp von Leptos unter der URL https://meryem9907.github.io/weatherapp/ erreichbar.

Ergebnis (CD)

6.4 Fazit

Die Umsetzung der CI/CD-Pipeline stand im Zentrum unseres Projekts und stellte einen entscheidenden Baustein für einen modernen, automatisierten Entwicklungs- und Veröffentlichungsprozess dar. Obwohl die eigentliche Entwicklung der Leptos-Anwendung abgeschlossen ist, zeigt die Einrichtung der Pipeline deutlich, wie essenziell Continuous Integration und Continuous Deployment heute für Softwareprojekte sind – unabhängig davon, ob sie sich noch in aktiver Entwicklung befinden oder lediglich stabil betrieben werden sollen.

Durch die CI-Komponente konnten wir sicherstellen, dass alle Codeänderungen automatisiert getestet und gebaut werden. Dadurch wurden potenzielle Fehler frühzeitig erkannt und der manuelle Aufwand für wiederholbare Prozesse deutlich reduziert. Die CD-Komponente ermöglichte darüber hinaus eine automatische Veröffentlichung der Anwendung auf GitHub Pages – eine einfache, aber effektive Lösung, um statische Webanwendungen direkt aus dem Repository bereitzustellen. Damit wurde der gesamte Weg vom Code bis zur öffentlich erreichbaren Anwendung vollständig automatisiert.

CI/CD ist in modernen Softwareprojekten längst Standard und wird in nahezu allen professionellen Entwicklungsumgebungen eingesetzt – ob in kleinen Open-Source-Projekten oder großen Enterprise-Anwendungen. Es existieren zahlreiche Alternativen zu GitLab CI/CD, wie etwa GitHub Actions, Jenkins, CircleCI, Travis CI oder Bitbucket Pipelines, die je nach Projektanforderungen und Infrastruktur zum Einsatz kommen können.

Zusammenfassend lässt sich sagen: Die Einrichtung der CI/CD-Pipeline mit Deployment auf GitHub Pages war nicht nur technisch sinnvoll, sondern auch aus Sicht moderner Softwarepraxis ein unverzichtbarer Schritt. Sie verdeutlicht, wie Automatisierung nicht nur Effizienz schafft, sondern auch Vertrauen und Stabilität in die Softwarebereitstellung bringt.

6.5 Probleme

Die Implementierung der GitLab-Pipeline stellte sich als deutlich anspruchsvoller heraus als zunächst erwartet, insbesondere im Hinblick auf die Einrichtung und Stabilität der Continuous Deployment-Komponente. Während der Umsetzung kam es wiederholt zu Instabilitäten des GitLab-Runners, der in unregelmäßigen Abständen abstürzte und somit die Ausführung der Pipeline unterbrach. Zusätzlich traten in der verwendeten WSL-Umgebung (Windows Subsystem for Linux) unerklärliche Fehler auf, die innerhalb des verfügbaren Zeitrahmens nicht vollständig analysiert oder behoben werden konnten.

Zur Aufrechterhaltung eines funktionsfähigen Workflows wurden daraufhin verschiedene Übergangslösungen umgesetzt. So wurde unter anderem ein neuer Runner registriert, um potenzielle Konfigurationsfehler auszuschließen, sowie ein regelmäßiger Neustart des GitLab-Runners mithilfe des Befehls gitlab-runner restart eingeführt, um Abstürze abzufangen und den Betrieb kurzfristig zu stabilisieren.

Ein weiteres Problem ergab sich aus der lokalen Ausführung des GitLab-Runners innerhalb eines Docker-Containers. Ist dieser Container nicht aktiv – beispielsweise weil das Host-System ausgeschaltet ist – bleibt die Pipeline im Status pending, da kein verfügbarer Runner gefunden werden kann. Eine dauerhafte Lösung dieses Problems konnte aus zeitlichen Gründen nicht umgesetzt werden, wäre jedoch durch den Einsatz eines stets verfügbaren, extern gehosteten Runners realisierbar.

6.6 Quellen

7. Lizenz

Dieser Text steht unter der Creative Commons Lizenz Namensnennung/Keine kommerzielle Nutzung

Das gitlab Repository für diesen Bericht liegt unter dem Link https://gitlab.informatik.hs-augsburg.de/dva/berichte-2025/05