Freitag, 26. Dezember 2025

Codexzier DirectSight - Entwicklung (Teil 1)

Dienst mit .NET für Raspberry Pi entwickeln

Was muss ich tun, um einen Raspberry Pi mit Kamera und zwei Servos über einen Windows-PC anzusteuern?

An dieser Stelle ist klar: Dieses Thema lässt sich nicht in einem kurzen Blogpost mit zwei oder drei Seiten vollständig abhandeln. In Zeiten von Künstlicher Intelligenz und sogenanntem Vibe Coding sind größere Vorhaben jedoch deutlich schneller umsetzbar als noch vor fünf Jahren.
Trotzdem habe ich festgestellt, dass selbst der Einstieg – obwohl er nur ein Teilschritt des Gesamtprojekts ist – überraschend zeitintensiv war. Insgesamt habe ich mehr als drei Stunden an einem Tag investiert. Mit dem entsprechenden Vorwissen hätte ich das vermutlich in 30 Minuten erledigt.

Genau darum geht es mir jedoch: Lernen mit Unterstützung von KI, um die Umsetzung und Ausführung der einzelnen Komponenten wirklich zu verstehen.



Was ich verwende

  • Raspberry Pi 4
  • Raspberry Pi Kamera Modul 3
  • Visual Studio 2026 oder JetBrains Rider 2025.3.1
  • C# mit .NET 10
  • GitHub inkl. GitHub Actions
  • Linux bzw. Raspbian OS

 


Anforderung

Ziel ist zunächst, auf dem Raspberry Pi einen Dienst zu betreiben, der das aktuell ein "Hello World!" ausgibt.
Auf einem Windows-PC soll mit der Consolen Anwendung der Text mit Datum und Uhrzeit abgerufen werden.

Der gesamte Aufbau soll innerhalb eines privaten Netzwerks funktionieren.

  


Dienstanwendung für Raspberry Pi

Für den Dienst auf dem Raspberry Pi verwenden wir zunächst das Template „Worker Service“ in Visual Studio bzw. „Services“ in Rider.


Neben .NET 10 sind zunächst keine weiteren Einstellungen notwendig. Ich habe jedoch zusätzlich die Option „Do not use top-level statements“ aktiviert – hier bin ich bewusst altmodisch.

Anschließend kann ein passender Projekt- und Solution-Name vergeben werden.

 


Testklasse mit Socket

Damit später eine Konsolenanwendung ein „Hello World!“ inklusive Datum und Uhrzeit empfangen kann, wird die Klasse TcpTestServer.cs angelegt.

 using System.Net;  
 using System.Net.Sockets;  
 using System.Text;  
 namespace CodexzierDirectSight  
 {  
   public static class TcpTestServer  
   {  
     private const int Port = 5000;  
     public static async Task StartAsync(CancellationToken stoppingToken, ILogger logger)  
     {  
       var listener = new TcpListener(IPAddress.Any, Port);  
       listener.Start();  
       logger.LogInformation($"TCP Server listening on port {Port}");  
       try  
       {  
         while (!stoppingToken.IsCancellationRequested)  
         {  
           if (listener.Pending())  
           {  
             var client = await listener.AcceptTcpClientAsync(stoppingToken);  
             _ = HandleClientAsync(client, logger);  
           }  
           else  
           {  
             await Task.Delay(100, stoppingToken); // kleine Pause  
           }  
         }  
       }  
       catch (OperationCanceledException) { } // Normaler Stop  
       finally  
       {  
         listener.Stop();  
       }  
     }  
     private static async Task HandleClientAsync(TcpClient client, ILogger logger)  
     {  
       await using var stream = client.GetStream();  
       var message = Encoding.UTF8.GetBytes($"Hello World! {DateTime.Now:U}\n");  
       await stream.WriteAsync(message, 0, message.Length);  
       client.Close();  
       logger.LogInformation("Sent Hello World to client");  
     }  
   }  
 }  

Anschließend muss in der Worker-Klasse eine Instanz von TcpTestServer eingebunden werden.

 namespace CodexzierDirectSight;  
 public class Worker(ILogger<Worker> logger) : BackgroundService  
 {  
   protected override async Task ExecuteAsync(CancellationToken stoppingToken)  
   {  
     _ = TcpTestServer.StartAsync(stoppingToken, logger);  
     while (!stoppingToken.IsCancellationRequested)  
     {  
       if (logger.IsEnabled(LogLevel.Information))  
       {  
         logger.LogInformation("Worker running at: {time}", DateTimeOffset.Now);  
       }  
       await Task.Delay(1000, stoppingToken);  
     }  
   }  
 }  

 


Shell-Script für Deployment

Damit die Installation später per Shell-Eingabe erfolgen kann, wird im Repository-Verzeichnis der Ordner deploy angelegt.



In diesem Ordner befindet sich die Datei install.sh, die im Grunde eine einfache Textdatei ist.

#!/usr/bin/env bash  
 set -e  
 APP_NAME="CodexzierDirectSight"  
 SERVICE_NAME="codexzierdirectsight"  
 INSTALL_DIR="/opt/$SERVICE_NAME"  
 SERVICE_FILE="/etc/systemd/system/$SERVICE_NAME.service"  
 GITHUB_REPO="Codexzier/CodexzierDirectSight"  
 ARCH="linux-arm64"  
 TMP_DIR="/tmp/CodexzierDirectSight-install"  
 echo "== Installing $APP_NAME =="  
 # Root-Check  
 if [ "$EUID" -ne 0 ]; then  
  echo "ERROR: Run as root (use sudo)"  
  exit 1  
 fi  
 # Architektur-Check  
 if [[ "$(uname -m)" != "aarch64" ]]; then  
  echo "ERROR: This installer supports only ARM64"  
  exit 1  
 fi  
 # Vorbereitung  
 rm -rf "$TMP_DIR"  
 mkdir -p "$TMP_DIR"  
 mkdir -p "$INSTALL_DIR"  
 # Latest Release URL ermitteln  
 echo "Fetching latest release info..."  
 DOWNLOAD_URL=$(curl -s https://api.github.com/repos/$GITHUB_REPO/releases/latest \  
  | grep browser_download_url \  
  | grep "linux-arm64" \  
  | cut -d '"' -f 4)  
 if [ -z "$DOWNLOAD_URL" ]; then  
  echo "ERROR: Could not find release artifact"  
  exit 1  
 fi  
 # Download  
 echo "Downloading $DOWNLOAD_URL"  
 curl -L "$DOWNLOAD_URL" -o "$TMP_DIR/app.tar.gz"  
 # Stop Service (falls vorhanden)  
 systemctl stop $SERVICE_NAME 2>/dev/null || true  
 # Entpacken  
 tar -xzf "$TMP_DIR/app.tar.gz" -C "$INSTALL_DIR"  
 chmod +x "$INSTALL_DIR/CodexzierDirectSight"  
 # systemd Service installieren  
 cat > "$SERVICE_FILE" <<EOF  
 [Unit]  
 Description=CodexzierDirectSight Worker Service  
 After=network.target  
 [Service]  
 ExecStart=$INSTALL_DIR/CodexzierDirectSight  
 WorkingDirectory=$INSTALL_DIR  
 Restart=always  
 RestartSec=5  
 KillSignal=SIGINT  
 SyslogIdentifier=CodexzierDirectSight  
 User=root  
 [Install]  
 WantedBy=multi-user.target  
 EOF  
 # systemd reload & start  
 systemctl daemon-reexec  
 systemctl daemon-reload  
 systemctl enable $SERVICE_NAME  
 systemctl start $SERVICE_NAME  
 echo "== Installation complete =="  
 systemctl status $SERVICE_NAME --no-pager  

 


GitHub Workflow einrichten

Um die Bereitstellung über GitHub zu ermöglichen, muss ein entsprechender Workflow eingerichtet werden.

name: Release Codexzier DirectSight  
 on:  
  push:  
   tags:  
    - 'release-*.*.*'  
 permissions:  
  contents: write  
 jobs:  
  build-release:  
   runs-on: ubuntu-latest  
   env:  
    APP_NAME: CodexzierDirectSight  
    PROJECT_PATH: CodexzierDirectSight/CodexzierDirectSight.csproj  
    RUNTIME: linux-arm64  
    CONFIGURATION: Release  
   steps:  
   - name: Checkout source   
    uses: actions/checkout@v4  
   - name: Setup .NET  
    uses: actions/setup-dotnet@v4  
    with:  
     dotnet-version: 10.0.x  
   - name: Restore dependencies  
    run: dotnet restore $PROJECT_PATH  
   - name: Publish (self-contained)  
    run: |  
      dotnet publish $PROJECT_PATH \  
       -c $CONFIGURATION \  
       -r $RUNTIME \  
       --self-contained true \  
       /p:PublishSingleFile=true \  
       /p:PublishTrimmed=false \  
       -o publish  
   - name: Prepare artifact  
    run: |  
      mkdir -p artifact  
      cp -r publish/* artifact/  
      echo "${GITHUB_REF_NAME}" > artifact/VERSION  
      tar -czf codexzierdirectsight-linux-arm64.tar.gz -C artifact .  
   - name: Create GitHub Release  
    uses: softprops/action-gh-release@v2  
    with:  
      files: codexzierdirectsight-linux-arm64.tar.gz  
      name: Release ${{ github.ref_name }}  
      generate_release_notes: true  


Bereitstellung

Die Bereitstellung ist so konzipiert, dass ein neuer Tag im GitHub-Repository erstellt werden muss.


Anschließend auf „Draft new release“ klicken.


Der Name des Tags muss der im Workflow definierten Konvention entsprechen:

release-..*


Danach Titel und Beschreibung für das Release ergänzen und auf „Publish Release“ klicken.


Wurde der Tag korrekt gesetzt, startet automatisch der Workflow. Unter Actions sollte ein neuer Eintrag sichtbar sein. Der orangefarbene, animierte Punkt signalisiert, dass der Build aktuell ausgeführt wird.


Nach wenigen Sekunden wechselt der Status auf Grün, und die Bereitstellung kann auf dem Raspberry Pi mit Raspbian OS abgerufen werden. 


Installation auf dem Raspberry Pi

Hinweis: Der Code Stand hat sich bereits verändert und sollte bereits nicht mehr das "Hello World" zurück geben. Siehe am Ende den Link zu dem Branch, welches den Version Stand zu diesen Blogpost wiedergibt.

Mit folgendem Befehl wird die Installation ausgeführt und der Dienst gestartet:

curl -sSL https://raw.githubusercontent.com/Codexzier/CodexzierDirectSight/master/deploy/install.sh | sudo bash 

 


Testen

Prüfen, ob der Dienst nach der Installation läuft:

 systemctl status codexzierdirectsight

Wichtig ist, dass der Status „active (running)“ in grüner Schrift angezeigt wird. Dann ist sichergestellt, dass der Dienst korrekt läuft.

Ausgabe der aktuellen Log-Einträge anzeigen (Abbruch mit STRG + C):

journalctl -u codexzierdirectsight -f

 


Hello World empfangen

Mit einer Konsolenanwendung unter Windows soll nun das „Hello World!“ inklusive Datum empfangen werden.
Dazu wird eine neue C#-Konsolenanwendung erstellt und folgender Code eingefügt:

internal class Program  
 {  
   static async Task Main(string[] args)  
   {  
     string raspberryIp = "192.168.178.88"; // IP Adresse of Raspberry Pi  
     int port = 5000;  
     using var client = new TcpClient();  
     await client.ConnectAsync(raspberryIp, port);  
     using var stream = client.GetStream();  
     byte[] buffer = new byte[1024];  
     int bytesRead = await stream.ReadAsync(buffer, 0, buffer.Length);  
     string message = Encoding.UTF8.GetString(buffer, 0, bytesRead);  
     Console.WriteLine($"Received from server: {message}");  
   }  
 } 

Nach dem Start sollte folgendes Ergebnis erscheinen:



Rückblick

Rückblickend muss ich sagen: Ohne ChatGPT hätte ich für das Installations-Shell-Script und den YAML-Workflow deutlich länger gebraucht. Vor allem das Verständnis der benötigten Befehle und des Zusammenspiels der einzelnen Schritte hätte mich viel Zeit gekostet.

Über klassische Google-Suchen finde ich solche Lösungen nur schwer – entweder fehlen mir die richtigen Schlagwörter oder die gesponserten Ergebnisse erschweren es, schnell zu beurteilen, ob eine Seite überhaupt relevant ist.

Codestand habe ich im ersten Branch abgelegt, dass zu dem Blogpost gehört:

Codexzier/CodexzierDirectSight at Create-Initialize-RaspberryPi-Service

Donnerstag, 6. Februar 2025

Spiele Entwicklung: Zurück zu MonoGame


In den Anfangszeiten wurde über die Google-Suche über den Begriff oft Ergebnisse zu Monogamie und Beziehung ausgegeben. Das hat sich inzwischen geändert und durch eine Artikelserie in der .NET Pro wurde mein Interesse wieder geweckt.

Grund meines Interesses liegt mit unter wieder über den Horizont zu schauen, weil Unity 3D vorletztes Jahr (2023) mit einen zweifelhaften Geschäftsmodel kam. Also war mein Ausweichen zunächst auf Unreal Engine und wegen fehlender Zeit kam ich zunächst nicht dazu.

Zwischen Weihnachten und Sylvester hatte ich endlich etwas Zeit und las die ersten Teile der Artikelserie in der .NET Pro und fing dann an wieder zu Entwickeln mit dem MonoGame Framework für Spiele Entwicklung.


Was braucht man für MonoGame?


Welches Ziel verfolge ich?

Spielideen und Spielmechaniken ausprobieren in einem kleineren und eingeschränkten Rahmen aufbauen.


Warum ein Map Editor?

Eines meiner Lernziele ist das Erstellen einer eigenen Spielkarte, auf dem ich meine Aufgaben und Ziele hinterlegen kann. Dazu gehört auch das Programmieren eines Tools, mit dem ich schnell einen Karten Abschnitt mit Aufgaben anlegen kann.


Wie geht’s weiter?

Zeitlich bin ich oft vollbeschäftigt. Aber hin und wieder komme ich schrittweise weiter und abgesehen davon macht das Programmieren spaß.


Repository: Codexzier/MiniShipDelivery

 

Fingerübungen - MS SQL Pivot

Nach drei Jahren MS SQL und ich finde mal wieder etwas, dass ich noch nicht kenne und ich auch nicht in der Schulung hatte. 

Bis vor heute, war mir nicht bewusst, dass man auch Pivot Ergebnisse in MS SQL schreiben kann.

Beispiel: Ich habe in der einen Spalte, mehrere verschiedene Themen, die ich hier Unkreativ "Sache 1" bis "Sache 3" genannt habe. Diese lassen sich am Ende so raus führen, dass am Ende daraus drei Spalten werden. Hätte ich das vorher gewusst, hätte ich viel bessere Abfragen schreiben können.

 DECLARE @myTable TABLE (Dinge VARCHAR(10), Menge INT)  
 INSERT INTO @myTable  
 (  
   Dinge,  
   Menge  
 )  
 VALUES  
 ( 'Sache 1', 10 ),  
 ( 'Sache 1', 2 ),  
 ( 'Sache 1', 5 ),  
 ( 'Sache 1', 77 ),  
 ( 'Sache 2', 10 ),  
 ( 'Sache 2', 1 ),  
 ( 'Sache 2', 2 ),  
 ( 'Sache 2', 3 ),  
 ( 'Sache 3', 10 ),  
 ( 'Sache 3', 10 ),  
 ( 'Sache 3', 10 )  
 SELECT [pvt].[Sache 1], pvt.[Sache 2], pvt.[Sache 3]  
 FROM @myTable t  
 PIVOT  
 (  
 SUM(Menge)  
 FOR Dinge IN([Sache 1], [Sache 2], [Sache 3])  
 ) AS pvt  

Mittwoch, 18. Dezember 2024

Tool Lagerverwaltung (Teil 9) - Upgrade

 
Ist schon etwas länger her, daß ich an dem Thema gearbeitet habe. Aber Arbeit geht vor und da gibt es viele Aufgaben zu erledigen.

Dennoch wollte ich mal wieder mein Application Framework weiter ausbauen und mußte nach einem Update mit Visual Studio feststellen, daß mein Projekt sich nicht mehr Kompilieren ließ. Aus irgendeinem Grund, können in .NET 7 keine .NET MAUI-Elemente mehr erkannt werden. Lösung ist ein Update. Aktuell ist .NET 9 raus, aber das lief erstmal nicht und daher habe ich mich entschieden erstmal auf .NET 8 zu Upgraden. Richtig ich mußte Upgraden. Einfach die .NET-Version anheben, hat nicht funktioniert. Und richtig gelesen, die Funktion heißt „Upgrade“, das sich doch etwas, wie "Migrieren" anfühlte. Weil mein Projekt nicht so viele Abhängigkeiten hat, war die Ausführung mehr den Button für Weiter zu klicken.

 

Rechte Maustaste auf das Projekt und dann im Kontext Menü "Upgrade" drücken.



Die Option "In-place project upgrade" kam meiner Anforderung am nächsten.



Auswahl ist .NET 8, da bin ich mir sicher, dass die Dinge funktionieren, die funktionieren sollen.


 

Auswahl der zu Upgradenen Inhalte so belassen.


 Und dann warten, bis alle Dateien bearbeitet wurden. In meinem Fall habe ich noch ein Problem mit SQLite, was aber die weitere Bearbeitung zunächst nicht stört.


 

Was ist das?

Brauche ich nicht, da ich nicht plane, die Anwendung auf einem Apple Gerät mit iOS zu entwickeln. Also weg mit dem drücken auf X. Und dann in die Projekt Eigenschaften öffnen.


 

Einfach den Haken rausnehmen.


 

Doch zu guter letzt

Bevor ich weiter am Projekt entwickeln kann, sollte die Unit Tests wieder laufen. Nur ein erfolgreicher Unit Test von sechs. Was allerdings auch noch sehr übersichtlich ist und auch der noch geringen Funktionalität der Anwendung zurückzuführen ist.



Ok, ich weiß wo ich stehen geblieben bin.


 

 

Donnerstag, 24. Oktober 2024

Fingerübungen - MS SQL Schnipselübung


Heute mal was sehr Kleines, das mehr als Fingerübung dient und auch die Aktivität meines Blogs zu halten. Ich bin mal wieder durch meine Notizen durch und suchte etwas, was ich an kleinen Sachen, die ich bereits angefangen habe. Und sehe da, ich habe hier noch ein paar SQL-Schnipsel.

In meinem aktuellen Projekt ist die Verwendung von SQL nicht weg zu denken und meine Skills haben sich inzwischen von Grundkenntnissen in Fortgeschritten gewandelt. Vom Experten bin ich noch weit weg, auch wenn andere der Meinung sind, daß man das auch nach wenigen Monaten sein kann. Aber die Anzahl der Jahre und Auseinandersetzen macht einen unterschied, wie sehr man in der Tiefe mit der Technologie vertraut ist und auch wie sehr die eine und andere Sprache sich im Verstand eingeübt hat.

Nichts für ungut, kommen wir zu einer kleinen Übungsaufgabe.


Tools

MS SQL Management Studio, Jetbrains DataGrip oder einem Tool für SQL


Übungsziel

Als Variable soll eine Tabelle angelegt werden, in der ein paar Einträge getätigt werden , Verarbeitet und am Ende Ausgeben. Hierbei soll keine Tabelle in der Datenbank angelegt werden. Praktisch bleiben die Daten nur für das Fertig Script in der Ausführung und werden nicht gespeichert.


Variable anlegen

Statt nur einen Wert in einer Variable zu hinterlegen, sollen gleich ein paar mehr Werte hinterlegt werden. Hier eignet sich die Variable als Tabelle anzulegen. Für die Eindeutigkeit eines Datensatz erhält die Tabelle eine Spalte für den Identifier oder auch Kurz ID, die mit der Einstellung IDENTITY wird von eins beginnend gezählt und mit jedem neuen Datensatz um einen hochzählt.

 DECLARE @myTable TABLE(  
      id INT IDENTITY(1, 1),  
      Hinweis VARCHAR(1024)  
 ); 

 

Werte eintragen

Das eintragen wird mit INSERT INTO ermöglicht.

 INSERT @myTable  
 (  
   Hinweis  
 )  
 VALUES  
 ('Dies ist der vorläufige text!'),  
 ('ersetzen'),  
 ('Dies ist was anderes!')  

 

Eintrag ändern

Nun soll Text hinzugefügt werden, die im Feld Hinweis mit 'Dies ist' im Anfang stehen.

 UPDATE mt  
 SET mt.Hinweis = mt.Hinweis + ' Hinzugefügter text'  
 FROM @myTable mt  
 WHERE mt.Hinweis LIKE 'Dies ist%'  

Datensatz überschreiben

Zweites Beispiel wie ein Feld überschrieben werden kann.

 UPDATE mt  
 SET mt.Hinweis = 'Ich bin ein neuer Satz'  
 FROM @myTable mt  
 WHERE mt.Hinweis = 'ersetzen'  


Ergebnis

Kommen wir zum Ende. Mit einem Select auf die Tabelle, kann nun das Ergebnis aus den verwendeten Vorgängen wiederspiegeln.

 SELECT *   
 FROM @myTable  

 

Zur Vollständigkeit

Nun das Ganze in einem Rutsch. Eine kleine Übung mit MS SQL, jedoch wurden hier nur die Grundbefehle verwendet, so daß dieses Beispiel auch mit anderen Datenbanken funktionieren sollte.

 -- Tabellen Variable anlegen  
 DECLARE @myTable TABLE(  
      id INT IDENTITY(1, 1),  
      Hinweis VARCHAR(1024)  
 );  
 
 -- Datensätze hinzufügen  
 INSERT @myTable  
 (  
   Hinweis  
 )  
 VALUES  
 ('Dies ist der vorläufige text!'),  
 ('ersetzen'),  
 ('Dies ist was anderes!')  
 
 -- Zwischen Stand ausgeben.  
 SELECT * FROM @myTable  
 
 -- Bedingt Test Update Text ergänzen. Dahinter anhängen  
 UPDATE mt  
 SET mt.Hinweis = mt.Hinweis + ' Hinzugefügter text'  
 FROM @myTable mt  
 WHERE mt.Hinweis LIKE 'Dies ist%'  
 
 -- Überschreiben  
 UPDATE mt  
 SET mt.Hinweis = 'Ich bin ein neuer Satz'  
 FROM @myTable mt  
 WHERE mt.Hinweis = 'ersetzen'  
 
 -- Ergebnis ausgeben  
 SELECT *   
 FROM @myTable  

Montag, 20. Mai 2024

Hello World, Blazor im Container auf Raspberry PI


Das liegt schon etwas her, dass ich damit anfangen wollte ein Blog Eintrag über das Thema Docker zu schreiben.

Die Ursprungsidee ist, dass auf dem Raspberry PI die Webseite gehostet wird und darüber der Text eingegeben wird und später über eine Desktop Anwendung auf dem Raspberry PI wieder gegeben wird auf dem Bildschirm. Doch zunächst muss die Webseite in einem Container gestartet werden mit einem ARM System.

 

Benötigt wird:

  • Raspberry PI 2 oder aktueller
  • RaspbianOS
  • Installation .NET 7 auf RaspbianOS
  • Docker installtion auf Raspbian

 

Beispiel Webseite

Auf dem Raspberry PI soll eine Blazor Web Anwendung gehostet über einen Docker Container gehostet werden.

 

Vorbereitung

Für den Zugriff auf den Raspbian verwende ich Putty und die SSH Verbindung.

 

Docker installieren

Ein Befehl reicht, um die aktuelle Version von Docker zu installieren auf dem Raspberry PI

 sudo curl -fsSL https://get.docker.com | sh  

 

Nach installation kann die Docker version abgefragt werden.

 docker --version   




.NET installieren und prüfen

Mit dem folgenden Befehl wird das .NET installiert auf RaspbianOS und kann einige Minuten dauern. In meinem Fall habe ich .NET 7 installiert und musste die Version nicht direkt eingeben.

 curl -sSL https://dot.net/v1/dotnet-install.sh | bash /dev/stdin --channel STS 

War die Installation erfolgreich, dann sollte das wie im folgenden Bild aussehen.


Damit der befehlt von das Abfragen der Versionsnummer funktioniert, sollte die Pfadauflösung geändert werden.

 echo 'export DOTNET_ROOT=$HOME/.dotnet' >> ~/.bashrc  
 echo 'export PATH=$PATH:$HOME/.dotnet' >> ~/.bashrc  
 source ~/.bashrc  

Die Versionsnummer abfrage sollte ohne "sudo" eingabe funktionieren.

 dotnet --version  

 



Blazor Projekt erstellen

Zum testen kann direkt auf dem Raspberry PI ein Blazor Projekt erstellt werden. Natürlich macht das mehr sinn, sein Projekt auf dem System zu erstellen, wo man selbst auch Programmiert.

 

Da auf dem RaspbianOS kein Visual Studio 2022 habe, kann ich mit Hilfe von .NET das Projekt direkt per Befehl erzeugen. Aber zunächst muss ein Ordner erstellt werden, in der später auch die Dockerfile Datei hinzugefügt wird.

 

Dann anschließend in das Verzeichnis wechseln: cd ExampleBlazorApp


Mit dem erstellen der Blazor WebAssembly Standalone Anwendung, wird wiederum ein Verzeichnis erstellt. 

 dotnet new blazorwasm -o ExampleBlazorProject  

Das erstellen dauert ca. eine halbe Minute. Wurde das erstellen zum ersten mal verwendet, werden am Ende deutlich Mehr Informationen angezeigt als im folgenden Bild.

 

Jetzt noch die Dockerfile erstellen im selben Verzeichnis mit dem Editor 'nano':

 nano Dockerfile 



Inhalt der Dockerfile. Hier ist darauf zu achten, dass die Dockerfile im selben Ordner steckt wie der Projekt Ordner selbst:

 # Build stage  
 FROM mcr.microsoft.com/dotnet/sdk:7.0 AS build  
 WORKDIR /src  
 # Copy the .csproj file and restore the dependencies  
 COPY ["ExampleBlazorProject/ExampleBlazorProject.csproj", "ExampleWebAssembly/"]  
 RUN dotnet restore "ExampleBlazorProject/ExampleBlazorProject.csproj"  
 # Copy the rest of the application code  
 COPY . .  
 WORKDIR "/src/ExampleBlazorProject"  
 RUN dotnet build "ExampleBlazorProject.csproj" -c Release -o /app/build  
 RUN dotnet publish "ExampleBlazorProject.csproj" -c Release -o /app/publish  
 # Final stage  
 FROM nginx:alpine AS final  
 COPY --from=build /app/publish/wwwroot /usr/share/nginx/html  
 EXPOSE 80  
 CMD ["nginx", "-g", "daemon off;"]  

 


Mit STRG + X kann die Anwendung dann beendet werden. Bevor jedoch die Anwendung 'Nano' geschlossen wird, wird abgefragt, ob die Änderung gespeichert werden soll.


Mit Y oder J Taste drücken…



… und anschließend Enter-Taste drücken. Fertig.

 

Nun sollte im Verzeichnis die Datei 'Dockerfile' vorhanden sein.


 

Nun sollte sich das Beispiel Projekt als Docker Container erstellen lassen. Der Name des Container muss komplett klein geschrieben sein.

 sudo docker build -t exampleblazorcontainer .  

Die Ausführung dauert eine weile (5 bis 6 Minuten), da einige Dinge Extrahiert werden und Inhalte geladen werden.



Nun der letzte Schritt, der Zeigt, dass man eine Blazor Anwendung komplett auf Linux erstellen kann.

 sudo docker run -d -p 8081:80 --name myblazorapp exampleblazorcontainer

Wenn alles geklappt hat, sollte über die Docker abfrage dann sichtbar sein, dass der Container läuft.




Schlusssatz

Ich hatte ganz vergessen, dass man seit .NET Core, eine Web Anwendung komplett in Linux aufstellen kann. Und so wie das jetzt ist, finde ich die Lösung mit Blazor sehr gelungen. Der Weg ist selbst nicht intuitiv und musste daher schon einiges Ausprobieren und recherchieren. Hilfreich war natürlich auch ChatGpt, welches sich wieder als sehr gutes Werkzeug macht und Zeit erspart, unzählige veraltete Forum Einträge durch zu lesen.

Eigentlich wollte ich das mit .NET 8. Doch zu diesen Zeitpunkt (20.05.2024) hatte ich noch Probleme mit der Version und hatte mich auch nicht länger beschäftigt.

 

Resourcen

Bereitstellen von .NET-Apps auf ARM-Single-Board-Computern - .NET | Microsoft Learn

ChatGpt

Sonntag, 7. April 2024

Feiertage berechnen


Eine Sache hatte ich noch offen mit den Feiertagen. Wenn man die Ostertage berechnet, kann auch der Rest der Feiertage berechnet werden.

Viele Feiertage sind auf ihr festgelegtes Datum fixiert. Bei einigen jedoch nicht und haben ihre Abhängigkeit zu den Ostertagen.

 

Benötigt

Rider, Visual Studio 2022 oder Visual Studio Code

Mindestens C# 10 (für TOP LEVEL Statement)

 

Feiertage berechnen

Mit der Eingabe des Jahres, sollen am Ende die Feiertage in der Console ausgegeben werden.

 

Wir feiern die Feste wie sie fallen

In meinem vorigen Blogeintrag habe ich bereits beschrieben, wie die Berechnung von den Osterfeiertagen Funktionen auf Gauß Osterformel setzt. Diese Berechnung wird für einige weitere ebenfalls verwendet. Bei den festen Feiertagen ist klar und unschön, wenn diese auf einen Samstag oder Sonntag fallen.

Zur Vollständigkeit habe ich auch die Berechnung des Ostertages mit reingenommen.

 Console.WriteLine("Calculate German Feiertage!");  
 Console.WriteLine("Enter year:");  
 var year = int.Parse(Console.ReadLine());  
 var feiertage = new List<PuplicHoliday>();  
 var ostermontag = CalculateOstermontag(year);  
 feiertage.Add(new PuplicHoliday(ostermontag, "Ostermontag", "BW, BY, BE, BB, HB, HH, HE, MV, NI, NW, RP, SL, SN, ST, SH, TH"));  
 // neujahr  
 feiertage.Add(new PuplicHoliday(new DateTime(year, 1, 1), "Neujahr", "BW, BY, BE, BB, HB, HH, HE, MV, NI, NW, RP, SL, SN, ST, SH, TH"));  
 // heilige drei könige  
 feiertage.Add(new PuplicHoliday(new DateTime(year, 1, 6), "Heilige Drei Könige", "BW, BY, ST"));  
 // internationaler frauentag  
 feiertage.Add(new PuplicHoliday(new DateTime(year, 3, 8), "Internationaler Frauentag", "BE, MV"));   
 // karfreitag  
 feiertage.Add(new PuplicHoliday(ostermontag.AddDays(-2), "Karfreitag", "SL"));  
 // ostersonntag  
 feiertage.Add(new PuplicHoliday(ostermontag.AddDays(-1), "Ostersonntag", "BB"));  
 // tag der arbeit  
 feiertage.Add(new PuplicHoliday(new DateTime(year, 5, 1), "Tag der Arbeit", "BW, BY, BE, BB, HB, HH, HE, MV, NI, NW, RP, SL, SN, ST, SH, TH"));  
 // christi himmelfahrt  
 feiertage.Add(new PuplicHoliday(ostermontag.AddDays(39), "Christi Himmelfahrt", "BW, BY, BE, BB, HB, HH, HE, MV, NI, NW, RP, SL, SN, ST, SH, TH"));  
 // pfingstsonntag  
 feiertage.Add(new PuplicHoliday(ostermontag.AddDays(49), "Pfingstsonntag", "BB"));  
 // pfingstmontag  
 feiertage.Add(new PuplicHoliday(ostermontag.AddDays(50), "Pfingstmontag", "BW, BY, BE, BB, HB, HH, HE, MV, NI, NW, RP, SL, SN, ST, SH, TH"));  
 // fronleichnam  
 feiertage.Add(new PuplicHoliday(ostermontag.AddDays(60), "Fronleichnam", "BW, BY, HE, NW, RP, SL"));  
 // mariä himmelfahrt  
 feiertage.Add(new PuplicHoliday(new DateTime(year, 8, 15), "Mariä Himmelfahrt", "BY, SL"));  
 // tag der deutschen einheit  
 feiertage.Add(new PuplicHoliday(new DateTime(year, 10, 3), "Tag der deutschen Einheit", "BW, BY, BE, BB, HB, HH, HE, MV, NI, NW, RP, SL, SN, ST, SH, TH"));  
 // reformationstag  
 feiertage.Add(new PuplicHoliday(new DateTime(year, 10, 31), "Reformationstag", "BB, HB, HH, MV, NI, SN, ST, SH, TH"));  
 // allerheiligen  
 feiertage.Add(new PuplicHoliday(new DateTime(year, 11, 1), "Allerheiligen", "BW, BY, NW, RP, SL"));  
 // buß- und bettag  
 feiertage.Add(new PuplicHoliday(ostermontag.AddDays(-47), "Buß- und Bettag", "SN"));  
 // 1. weihnachtstag  
 feiertage.Add(new PuplicHoliday(new DateTime(year, 12, 25), "1. Weihnachtstag", "BW, BY, BE, BB, HB, HH, HE, MV, NI, NW, RP, SL, SN, ST, SH, TH"));  
 // 2. weihnachtstag  
 feiertage.Add(new PuplicHoliday(new DateTime(year, 12, 26), "2. Weihnachtstag", "BW, BY, BE, BB, HB, HH, HE, MV, NI, NW, RP, SL, SN, ST, SH, TH"));  
 var orderedFeiertage = feiertage.OrderBy(x => x.HolidayDate);  
 foreach (var holiday in orderedFeiertage)  
 {  
   Console.WriteLine($"Datum: {holiday.HolidayDate.ToShortDateString()}, Beschreibung: {holiday.Description}, Bundesländer: {holiday.Bundeslaender}");  
 }  
 Console.ReadLine();  
 // statische methode berechnet den ostermontag  
 static DateTime CalculateOstermontag(int year)  
 {  
   var a = year % 19; // Zyklus  
   var b = year % 4;  // Schaltjahr  
   var c = year % 7;    
   var k = year / 100;   
   var p = (8 * k + 13) / 25;   
   var q = k / 4;   
   var m = (15 + k - p - q) % 30; // Korrektur Sonntag und Montag gleichung  
   var n = (4 + k - q) % 7;    // Korrektur Sonnengleichung  
   var d = (19 * a + m) % 30;   // Mondentfernung  
   var e = (2 * b + 4 * c + 6 * d + n) % 7;  
   var day = 22 + d + e;  
   return (new DateTime(year, 3, 1)).AddDays(day);  
 }  
 public class PuplicHoliday  
 {  
   public PuplicHoliday(DateTime holidayDate, string description, string bundeslaender)  
   {  
     this.HolidayDate = holidayDate;  
     this.Description = description;  
     this.Bundeslaender = bundeslaender;  
   }  
   public string Bundeslaender { get; }  
   public string Description { get; }  
   public DateTime HolidayDate { get; }  
 } 

 

Wird der Code ausgeführt, sollte am Ende in der richtigen Reihenfolge, die Feiertage im Consolen Fenster zu lesen sein.



 

Zum Schluss

Wenn man das einmal durch gegangen ist, ist die Berechnung der restlichen der Feiertage simple umzusetzen.

 

Referenzen

Gesetzliche Feiertage in Deutschland – Wikipedia

 

Plattform erweitern für den Arduino UNO Q

  So ganz platt auf dem Gridfinity Feld funktioniert, wenn man bereits ein paar Gridfinity Schalen stapelt. Also kommen ich zum nächsten Sch...