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
Kommentare