Posts mit dem Label C# werden angezeigt. Alle Posts anzeigen
Posts mit dem Label C# werden angezeigt. Alle Posts anzeigen

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

 

Dienstag, 28. September 2021

Error Text ausgeben bei einer Stored Procedure in C# Anwendung


Vielleicht ist das, worauf ich diesmal eingehe selbstverständlich, aber ich habe zu dem Thema ein wenig gesucht und mal wieder nichts gefunden. Wenn ich in meiner Anwendung eine Stored Procedure ausführe, dann würde bei einem Fehler keine Rückmeldung von der Ausgeführten Prozedur kommen und der Eindruck entsteht, dass die gespeicherte Prozedur normal durchgelaufen ist. Nun kann man auch einen Output Wert einrichten und erwarten, das ein entsprechender Wert herauskomm. Käme nun ein Standard Wert heraus, könnte daraus geschlossen werden, dass etwas in der Prozedur nicht gemacht macht wurde. Ohne Detail Information, kann man alles vermute. Fehler im Script, keine Ausreichende Berechtigung vergeben oder Datensatz hat einen Fehler. Was auch immer. Schöner wäre einen Fehlertext zu erhalten.

 

Benötigt

  • MS SQL Datenbank
  • Visual Studio 2019

 

Ziel

Das Beispiel soll ein Wert zurück geben, wenn die Stored Procedure ohne Fehler ausgeführt wurde. Bei einem Ausnahmefehler soll wiederum der Fehler Text Ausgeben werden in der Anwendung.

 

Beispiel Prozedur anlegen

Die Folgende Prozedur kann in ihrer Ausführung normal durchlaufen und den erwarteten Ausgangs Wert in 'myGetStatusMessage' ausgeben. Wird der Parameter 'showMeErrorMessage' auf 1 gesetzt, dann wird eine Ausnahme geworfen.

 USE [example-data]   
 GO  
 SET Ansi_Nulls ON  
 GO  
 CREATE PROCEDURE [dbo].[myStoredProcedure]    
   @showMeErrorMessage AS BIT,   
   @myGetStatusMessage AS NVARCHAR(20) OUTPUT,     
   @myStoredProcedureErrorMessage NVARCHAR(100) OUTPUT   
 AS  
 Begin Try  
   IF @showMeErrorMessage = 1  
   Begin  
     THROW 123, 'Eine Ausnahme wurde geworfen', 1;   
   end  
   SELECT @myGetStatusMessage = 'Alles in Ordnung!'   
 end try  
 begin catch  
   SELECT @myStoredProcedureErrorMessage = ERROR_MESSAGE();  
 end catch  

 

Beispiel C# Anwendung

Nun zu einer Consolen Anwendung, die den Aufruf einer gespeicherten Prozedure ausführen kann und auch unter Ziel Bedingungen den Fehlertext ausgeben kann, dass durch den Catch Block der Prozedur ausgefüllt wurde.

 

 using System;  
 using System.Data.SqlClient;  
 Console.WriteLine("Ein Beispiel wie man eine Error Meldung aus einer Store Procedure ausgegeben werden kann");  
 Console.WriteLine("Store Procedure wird ausgeführt ohne Ausnahmefehler!");  
 RunStoreProcedure(false);  
 Console.WriteLine("\r\nStore Procedure wird ausgeführt mit Ausnahmefehler!");  
 RunStoreProcedure(true);  
 Console.WriteLine("\r\nPress enter to close application");  
 Console.ReadLine();  
 static void RunStoreProcedure(bool throwError)  
 {  
   var connection = new SqlConnection(  
     @"server=127.0.0.1,1433;User Id=CustomUser;Password=passwort;Database=example-data;");  
   connection.Open();  
   var query = "DECLARE @StatusOutput AS NVARCHAR(20), @ErrorOutput AS NVARCHAR(100);" +  
         $"EXEC [dbo].[myStoreProcedure] {(throwError ? 1 : 0)}, " +  
         "@StatusOutput OUTPUT, " +  
         "@ErrorOutput OUTPUT; " +  
         "SELECT @StatusOutput AS Status, @ErrorOutput AS Error";  
   var command = new SqlCommand(  
     query,   
     connection);  
   var reader = command.ExecuteReader();  
   while (reader.Read())  
   {  
     Console.Write("Ergebnis: ");  
     for (var iColumn = 0; iColumn < reader.GetColumnSchema().Count; iColumn++)  
     {  
       Console.Write($"{reader.GetValue(iColumn)}, ");  
     }  
     Console.WriteLine("");  
   }  
   connection.Close();  
 } 

 

That's it

Der Umfang ist übersichtlich und kann in den meisten Fällen für eine Anwendung in der Form eine Store Procedure ausführen. Was nicht so einfach ist, wenn eine Tabelle zurück gegeben wird und dort die Möglichkeit statt der Ergebnis Tabelle, wiederum einen Fehlertext auszugeben.

 

GitHub - ExampleStoreProcedureErrorOutput

Sonntag, 16. August 2020

Analog Digital Wandler ADS1115 einlesen (Meadow)

Meistens, wenn ich ein Modul finde, das ich noch nicht kenne, dann existiert bereits eine Bibliothek, die in C/C++ geschrieben ist.

Schaut man im .NET Umfeld, dann ist wiederum meistens nichts zu finden. Damit ist mein Interesse geweckt, etwas darüber zu schreiben und natürlich ein Beispiel-Code hier auf meinem Blog zu veröffentlichen.

Seit fast einem Jahr liegt bei mir der Meadow und läuft aktuell mit dem Beispielprogramm. Dieser Controller zielt auf ‚Internet of Things‘- Themen ab und ist der direkte Nachfolger vom Netduino. Im Gegensatz zu dem Netduino, kann mit dem Meadow in der aktuellen C# (8.0) und .NET Framework (4.7.3) Version geschrieben werden. Dies bedeutet auch, dass gegenüber dem veraltetem .NET Micro Framework vieles neu aufgebaut wurde und folglich neue Lösungen geschrieben werden können.

 Benötigt:

  • Meadow
  • Breadboard
  • Jumper Leitungen
  • Potentiometer (oder alternative ein Widerstand und ein Lichtempfindlicher Widerstand)
  • ADS1115

 

Kurzes über den AD Wandler

Bevor ich mit der Zielbeschreibung loslege, müssen einige Details zu dem AD Wandler genannt werden. Beispielsweise, wie hoch darf die Eingangsspannung sein, die auf den vier Eingängen angelegt wird?

Betriebsspannung (VDD)

2V bis 5,5V

Stromaufnahme (Continuous Mode)

150uA

Industrieschnittstelle

I²C

Adresse

0x48 (Kann geändert werden durch Pin Select)

Eingänge

4

Abtastrate

8 bis 860

Eingangsspannung

-0,3V bis +0,3V über der Betriebsspannung (VDD)

Eingangsstrom

10mA bei fortlaufender Messung, 100mA bei einzel Messung

Für manche Anwendung ist auch die Betriebstemperatur wichtig, die hier von -40°C bis +125°C sein darf.


Anforderung

An einem Potitiometer soll die anliegende Teilspannungshöhe geändert werden. Die abgeleitete Teilspannung soll am Eingang A0 eingehen. Über einen Methoden Aufruf soll der Eingang eingelesen und dessen Wert über die Output Konsole ausgegeben werden.

 

Schaltungsaufbau

Im folgendem Bild ist zu sehen, dass ich kein Potitiometer verwende (schlichtweg, weil ich keinen hatte). Alternativ verwende ich einen 10k Ohm Widerstand und einen üblichen Lichtempfindlichen Widerstand (Lichtsensor 30 - 90 KOhm).


Verwenden von bestehenden Informationen

Zuerst wollte ich den bestehenden C++ Code migrieren nach C# und .NET Framework. Das bedeutet im Grunde, dass ich die Methodennamen und die #define Direktiven mit dessen Biteinstellungen (HEX Werte) direkt übernehme. Aber nach und nach stellte ich fest, dass einige Inhalte sich auf andere Module wiederum bezogen und ich immer wieder im Datenblatt nach sehen musste, was eigentlich die einzelne Biteinstellung für das Modul bedeutet. Ein Beispiel ist die Standard Einstellung 128SPS (Samples per second) für den ADS1115 Modul, aber im Adafruit Code steht etwas von 1600SPS.

Das Gute daran ist, man erarbeitet sich selbst eine Lösung, wie die Einstellungen abgebildet werden. Das macht den Aufbau der Klasse einfacher und die Beschreibungen zielgenauer.


Programm code

Im folgenden Beispiel aus dem ‚SimpleCode‘-Branch wurde nur das übernommen, was den Standard- Einstellungen entsprach. Z.B. Bitstellungen, die mit 0 belegt sind (bzw. in HEX 0x00 Wert) mussten nicht sein, da das Anlegen der Konfiguration immer mit 0 beginnt. Der folgende Code ist aus der Klasse ‚SimpleAds1115‘ und hat für den Abruf eine öffentliche Methode ‚ReadSingleInputValue‘ zum Abruf der anliegenden Spannung. Die Parameter-Eingabe bestimmt, welcher Eingang gelesen wird.

 internal class SimpleAds1115 : App<F7Micro, SimpleAds1115>  
 {  
   private readonly II2cPeripheral _i2CPeripheral;  
   public SimpleAds1115()  
   {  
     var i2CBus = Device.CreateI2cBus();  
     this._i2CPeripheral = new I2cPeripheral(i2CBus, 0x48);  
   }  
   internal ushort ReadSingleInputValue(AdsInput input)  
   {  
     // disable the comparator  
     // Single-shot mode (default)  
     // Operational status/single-shot conversion start  
     // - Begin a single conversion  
     ushort config = 0x8103;  
     config |= (ushort)input;  
     byte[] data = new byte[] { (byte)(config >> 8), (byte)(config & 0xff) };  
     this._i2CPeripheral.WriteRegisters(0x01, data);  
     Thread.Sleep(9);  
     var result = this._i2CPeripheral.ReadRegisters(0x00, 2);  
     return (ushort)((result[0] << 8) | result[1]);  
   }  
 }  

Vollumfänglich wird später die Klasse ‚Ads1115‘, die zu dem jetzigen Zeitpunkt nicht fertig ist.


Auf den Meadow schreiben

Sobald die Anwendung über Deploy auf dem Meadow geschrieben wurde, kann über das Fenster 'Output' das Ergebnis der anliegenden Spannung gelesen werden. Mit der Static Methode ‚GetVoltage‘ wird der gelesene Wert umgerechnet in Volt. Den Wert 22610 entnahm ich bei einem Eingangssignal von 5V. An der Stelle könnt Ihr den zu kalibrierenden Wert ersetzen, wenn bei Euch mit 5V ein anderer Wert gemessen wird.

 


Vorsicht mit der maximalen Eingangsspannung

In Standard Einstellungen sind die Eingänge bis 6,144V. Bei dieser Einstellung sollte nicht mehr als 0,3V anliegen, weil dies zur einer Beschädigung des Moduls führen könnte.


Fazit

Zunächst das Offensichtliche:  wenn das eigene Projekt mehr Analog-Digital Wandler benötigt, dann ist dieses Modul genau richtig. In einem I²C Bus können bis zu vier Module adressiert werden. Doch dieses Modul hat noch weitere Funktionen, um sich auf die Bedingungen eines Projektes besser anzupassen. Die Eingänge können auf gezielte Maximal-Spannungen eingestellt werden. Die Anzahl der Messungen pro Sekunde gehen bis zu 860SPS (Samples per second). Die vier Eingänge werden durch ein Multiplex Funktion angesprochen, so das von vier Eingänge auf zwei Eingänge umgestellt werden kann. Mit dem einstellbaren Komparator kann auf spezifische Bedingung reagiert werden.

 

Meadow Code
Github – ExampleAds1115 (SimpleCode Branch)

Montag, 16. März 2020

Back to the Gadgeteer


Nachdem ich mit dem alten Lego Technic Control Center von 1990 den Plotter aufgebaut habe, will ich diesen auch steuern können. Leider hatte ich den Lego-Computer nicht mehr und für meinen heutigen Bedarf war er auch nicht mehr so interessant. Bei den vielen Microcontrollern, die ich heute besitze, stellte sich dann nur die Frage: Welcher sollte es werden?
Schließlich muss die Steuerung mit einem kleinen System funktionieren. Mit Lego Power Functions und Adapterkabel funktionierte das alles ganz gut, aber es stand kein Steuerungssystem dahinter, das ein komplettes Programm abspielen konnte.

Die Wahl hierfür fiel auf mein altes Gadgeteer FEZ Spider, welches mit C# und .NET Micro Framework programmiert werden kann, wie bei den Netduino. Hier fehlte mir zwar noch ein Motor Treiber, aber das kann ich noch nachträglich bauen.
Für mich war wichtig, dass ein Bildschirm zur Verfügung stände, mit dem man das abzuspielende Programm auswählen konnte.

Benötigt wird:
- Aktuelle Downloads
     netmf
     Microsoft's Gadgeteer Core (Doku und Beispiele)
     GHI Eletronics' NETMF SDK
- Alte Version, falls die Downloads nicht klappen. Zip enthält:
     netmfvs.vsix
     MicroFramorkSDK.MSI
     NETMF and Gadgeteer Package 2013 R3
- Ziel System Gadgeteer FEZ Spider und das Touch Display

Anforderung
Ziel soll eine Toucheingabe mit anschließend folgender Textausgabe "Button wurde gedrückt" für 3 Sekunden.

Wegen Zeitmangel vergessen
Vielleicht hatte ich zu viele Ideen und Baustellen, so dass ich vergessen habe, wie toll das System eigentlich ist. Mir fehlte zwar ein passendes Gehäuse, aber das sollte nicht hinderlich sein. Zunächst soll das System wieder zum Laufen gebracht werden.

Download Probleme
Wir leben im Jahr 2020, aber der Download wird bei GHI gehandelt, als hätten wir Anfang 2000 und DSL findet nach und nach seine Verbreitung in Deutschland. Viele Programme erfordern mehrere Gigabyte Daten für den Download, was inzwischen normal ist. Ärgerlich ist, wenn dies nicht nur langsam ist, sondern auch der Download abbricht oder fehlerhaft ist, und somit der Download von vorne anfangen muss. Damit der eine oder andere nicht ebenfalls diese Odyssey durchlaufen müssen, habe ich alle erforderlichen Inhalte als ZIP zusammengefasst.

Installation GHI Electronics NETMF SDK 2016 R1
Da sich mit der Zeit einige Links verändert haben, können zu einigen Inhalte die Daten nicht abgerufen werden. Am Besten in Advance, alles aushaken und nur das Ziel 'Framework' auswählen für die Installation.



Falls noch nicht installiert, dann auch "Install USB Drivers" mit anhaken.



Fehlendes Template
Nach den langen Downloads (insbesondere die 3,4GB netfm.zip) und Installation, kann zum Testen ein Beispielprogramm geschrieben werden. Visual Studio 2013 öffnen und neues Projekt erstellen. Pustekuchen, kein Template für Gadgeteer.




Template alternative
Nach der Suche im Internet und zahlreicher -nicht erfolgreicher- Installation, bzw. dem Prüfen, welche Installation nicht erfolgreich war, habe ich ein altes Projekt von mir genommen und daraus ein Template erstellt, das für den Gadgeteer FEZ Spider ausgelegt ist. Das war am Ende deutlich einfacher, als den Fehler in der Installation zu suchen.

Gadgeteer Template (Beinhaltet auch das Beispiel für das folgende Programm)

Die Datei "GadgeteerTemplate.zip" einfach in den Ordner "..\Dokumente\Visual Studio 2013\Templates\ProjectTemplates\Visual C#" einfügen. Anschließend kann dann Visual Studio 2013 gestartet werden und ein neues Projekt erstellt werden. Nun sollte auch das von mir erstellte Gadgeteer Template zu sehen sein.



Namen vergeben und dann wird die neue Solution erstellt. Das Template selbst kommt bereits mit der Beispiel-Anforderung und daher ist auch schon etwas Programmcode in der Program.cs vorhanden.

public partial class Program  
{  
     Text text;  
     void ProgramStarted()  
     {  
       CreateButton();  
     }  
     private void CreateButton()  
     {  
       var main = this.Display_T35.WPFWindow;  
       var canvas = new Panel();  
       canvas.HorizontalAlignment = HorizontalAlignment.Left;  
       canvas.SetMargin(4);  
       canvas.SetMargin(5, 5, 0, 0);  
       Rectangle rec = new Rectangle(200, 100);  
       rec.HorizontalAlignment = HorizontalAlignment.Left;  
       rec.VerticalAlignment = VerticalAlignment.Top;  
       rec.Fill = new SolidColorBrush(Colors.Green);  
       canvas.Children.Add(rec);  
       Font baseFont = Resources.GetFont(Resources.FontResources.NinaB);  
       text = new Text(baseFont, "Hello World!");  
       text.ForeColor = Colors.Black;  
       text.HorizontalAlignment = HorizontalAlignment.Left;  
       text.VerticalAlignment = VerticalAlignment.Top;  
       text.SetMargin(10, 10, 0, 0);  
       text.Height = 20;  
       text.Visibility = Visibility.Collapsed;  
       canvas.Children.Add(text);  
       main.Child = canvas;  
       main.TouchDown += new Microsoft.SPOT.Input.TouchEventHandler(main_TouchDown);  
     }  
     void main_TouchDown(object sender, Microsoft.SPOT.Input.TouchEventArgs e)  
     {  
       text.Visibility = Visibility.Visible;  
     }  
}


Verbindungen herstellen
Eins der super gemachten Dinge für die Entwicklung ist das Nutzen eines Designers, um die bestehenden Module per klicken und ziehen zu konfigurieren. Außerdem bekommt man so auch einen Plan, wie die vorliegende Hardware mit einander verbunden wird. Mit Doppelklick auf Program.gadgeteer öffnet sich die grafische Darstellung.



Rauf auf das Gadgeteer
Nach dem Anschließen des Gadgeteer sollte in den Eigenschaften des Projektes nun ein Gerät aufgelistet werden, das dann auch automatisch ausgewählt wurde.



Hello World!
Ist der Gadgeteer mit dem Display vorbereitet und mit dem PC per USB verbunden, kann das Beispielprogramm auf den Gadgeteer bereitgestellt werden. Und fertig.




Nicht aufgeben
Das Gadgeteer einstecken über USB Mini, erfolgte zunächst ohne ein Windows Sound. Keine Verbindung, vielleicht lag es mit USB 3.0 über dem HUB, was natürlich Unsinn war, dann eben mit einem anderen USB Mini Kabel, mit demselben Ergebnis. Und doch lag die fehlende Verbindung am USB. Zum Glück hatte ich den Kasten mit alten USB Kabeln noch nicht weg geworfen und fand noch ein USB Kabel mit einem USB Mini Anschluss und dieser funktionierte dann tatsächlich. Ich hatte davon schon mehrmals gehört und gelesen, aber noch nie selbst erlebt, seit es USB auf dem Markt gab.

Eigentlich ein schönes System
Das Gadgeteer FEZ Spider habe ich vor ungefähr fünf Jahren gekauft und nicht viel zum Experimentieren genutzt. Eigentlich schade, denn das System ermöglicht einen schnellen Einstieg für Software Entwicklung mit Hardware und man wird mit Hilfe des Designer auch geführt, die richtigen Verbindungen herzustellen. Alle Module die es dazu gibt, kommen mit einem eigenen Treiber, bzw. sind als Klassen verfügbar. Das Auseinandersetzen von Industrieschnittstellen ist daher für den Anfang nicht zwingend notwendig und erzielt durch das Plug&Play System schnell Ergebnisse.
Leider ist mir das Thema untergegangen und ich habe in der Vergangenheit noch nichts darüber geschrieben. Vielleicht kann ich das nachholen.

Ein paar Links:

Montag, 25. November 2019

USB Experiement Interface board von Velleman


Diesmal dürfte das etwas mehr retro (eine Zeitreise - back in time) sein.

Dieses Board kaufte ich vor 14 Jahren, als ich noch in der Ausbildung war. Mit dem Programmieren kam ich bis dato schlecht voran, aber ich wollte das können. Visual Studio 2005 war zu der Zeit aktuell und die Sprache C# programmierte ich noch sehr anfänglich. Ziel war damals einen Roboterarm zu bauen, aus dem nie was wurde. Dennoch wusste ich, welche Dinge ich zunächst lernen musste, damit ich mich meinem Ziel nähern konnte. Doch bevor ich damals mit der beiliegenden Test-Software etwas anstellen konnte, musste der Bausatz zusammengelötet werden.
Nun kam das Wochenende und ich suchte eine Lösung in C# und wurde nach langer Suche fündig. Ich fand einen Beispielprogrammcode, wie ich auf die beiliegende DLL zugreifen konnte und somit das Board auslesen und steuern konnte. Fast das ganze Wochenende verbrachte ich damals mit dem Programmieren eines eigenen Tools, dass auf C#, .NET Framework 2.0 und WindowsForms setzte.


Gegenwart
Aus heutiger Sicht, war der Programmcode, den ich damals geschrieben hatte, sehr einfach und dient nicht gerade als Beispiel-Programm. Deshalb entschied ich mich ein neues Beispiel-Programm zu schreiben, bevor ich das Board jetzt zeitnah bei eBay zum Verkauf anbiete.

Ein Teil davon wird neu sein
Ich fand in meinen Sicherung tatsächlich mein erstes Programm wieder, aber was ich an Programmcode entdeckte, sollte auch in der Sicherung bleiben. Deshalb kopierte ich den wesentlichen Programmcode. Für die GUI verwende ich WPF und kein WindowsForms mehr.
Das Ziel ist wieder ein Tool zu haben, dass die Verbindung herstellen kann. Die Steuerung sollte das Einschalten der Ausgänge und das Lesen der Eingänge ermöglichen.

Verbindung zu K8055D.dll
Im folgenden gezeigten Programmcode aus der Klasse ComponentK8055D.cs, wird über das Attribute 'DllImport' auf die Methode zu gegriffen. Die aufgezeigten Methoden greifen auf die Grundfunktionen zu und sind daher die Wesentlichen.

  public class ComponentK8055D : IDisposable  
   {  
     #region add dll  
     [DllImport("K8055D.dll")]  
     private static extern int OpenDevice(int CardAddress);  
     [DllImport("K8055D.dll")]  
     private static extern void CloseDevice();  
     [DllImport("K8055D.dll")]  
     private static extern bool ReadDigitalChannel(int Channel);  
     [DllImport("K8055D.dll")]  
     private static extern void SetDigitalChannel(int Channel);  
     [DllImport("K8055D.dll")]  
     private static extern void ClearDigitalChannel(int Channel);  
     [DllImport("K8055D.dll")]  
     private static extern int ReadAnalogChannel(int Channel);  
     [DllImport("K8055D.dll")]  
     private static extern void OutputAnalogChannel(int Channel, int Data);  
     #endregion  

Der Rest der Klasse führt die Funktionen der DLL aus und fängt Werte ab, die nicht passen. Die Methoden selbst haben entsprechen einen XML Kommentar, um beim Aufruf der Methoden zu zeigen, welche Eingaben für die Parameter gültig sind.

     #region public methods  
     
     public int Open(bool sk5, bool sk6)  
     {  
       int result = 3;  
       result -= sk5 ? 1 : 0;  
       result -= sk6 ? 2 : 0;  
       OpenDevice(result);  
       return result;  
     }  
     
     internal void Close() => CloseDevice();  
     
     public void WriteOutputDigital(int outputNumber, bool setupOnOff)  
     {  
       outputNumber = this.MinMaxValue(outputNumber, 1, 8);  
       if (setupOnOff)  
       {  
         SetDigitalChannel(outputNumber);  
       }  
       else  
       {  
         ClearDigitalChannel(outputNumber);  
       }  
     }  
     
     public void WriteOutputAnalog(int outputNumber, int value)  
     {  
       outputNumber = this.MinMaxValue(outputNumber, 1, 2);  
       value = this.MinMaxValue(value, 0, 255);  
       OutputAnalogChannel(outputNumber, value);  
     }  
     
     public bool ReadDigitalInput(int inputNumber)  
     {  
       inputNumber = this.MinMaxValue(inputNumber, 1, 5);  
       return ReadDigitalChannel(inputNumber);  
     }  
     
     public int ReadAnalogInput(int inputNumber)  
     {  
       inputNumber = this.MinMaxValue(inputNumber, 1, 2);  
       return ReadAnalogChannel(inputNumber);  
     }
  
     #endregion  
     #region private methods
  
     private int MinMaxValue(int value, int limitMin, int limitMax)  
     {  
       if(value < limitMin)  
       {  
         value = limitMin;  
       }  
       if(value > limitMax)  
       {  
         value = limitMax;  
       }  
       return value;  
     }  
     #endregion  
     public void Dispose() => CloseDevice();  
   }  

Der Rest des Programmcodes, dass die Beispiel Anwendung zusammenstellt, hat keine besonderen Aufbau. Daher gehe ich an dieser Stelle nicht weiter ein.

Die Beispiel Anwendung
Mit der Anwendung können nach dem Verbindungsaufbau über USB, die Ein- und Ausgänge des Boards gesteuert oder gelesen werden.
Die Verbindung erfolgt über den Button 'Connect'. Hier ist jedoch zu beachten, wie die Jumper auf dem Board gesetzt sind. Etwas weiter links auf dem Board, ist eine Tabelle zu sehen, die Beschreibt, welche Adresse das Board bekommt, wenn die Jumper gesetzt entsprechend gesetzt wurden. Die vier Adressen ermöglichen auch wirklich vier von solchen Boards an einem PC betreiben zu können.











Intervall Abfrage ist nach dem Start der Anwendung auf 10ms eingestellt. Stellt die häufige Abfrage ein Problem dar, ist zu empfehlen, die Intervall Abfrage auf 100ms oder 1000ms, um zustellen, bevor die Verbindung hergestellt wird.











Hergestellte Verbindung
Nach dem Clicken des Buttons 'Connect' vergehen ein bis zwei Sekunden, bis die Anwendung Ergebnisse Zeigt, wie das Ausmessen der Analog-Eingänge.
Auf dem Board, sind an den Analog-Eingängen zwei Potentiometer, an dem man das Eingangspegel verstellen kann. Wird mit einem Kreuzschlitzschraubendreher daran gedreht, sollte sich in der Anwendung der Balken unter Analog Inputs verändern.










Mit dem Drücken der Buttons, sollten sich auf der GUI für 'Digital Inputs', die Schwarzen Rechtecke in grüne einfärben.












Die Digitalausgänge werden einfach per Checkbox ein- oder ausgeschaltet.

























Bei den analogen Ausgängen, werden die Slider verstellt. Je weiter nach rechts gestellt, umso heller wird die LED am Analogen Ausgang.




















Bekommt man das Board noch
Tatsächlich ja. Also wenn man das Board nicht gerade über eine Ebay Auktion bekommt, sollte der Artikel auch über Conrad Elektronik oder Reichelt zu finden sein. Viel hat sich nicht geändert. Vielleicht die Farbe, aber die Funktionalität dürfte noch die selbe sein. Falls nicht die Ansteuerung mit der LED und dessen Methodennamen sich nicht geändert haben. Das Board, dass ich verwendet habe, hat die Bezeichnung 'P8055-1'. Weicht diese ab, dann sollte man gegebenenfalls die DLL   austauschen und für dies Beispiel-Anwendung durch die mitgelieferte DLL von der CD in die Visual Studio Projekt ersetzen. 

Programmcode wie immer
Natürlich stelle ich den Programmcode wieder auf meinem Github Repository bereit.

Nur Anwendung
Wer nur die Anwendung braucht, kann über diesen Link auch einfach das Programm herunterladen, installieren und dann Ausführen.


Sidequest: Rot gegen Blau

Eine weitere Wertfreie Umsetzung mit Claude ist meine Idee zu einer Simualtion, die den ganzen Tag läuft und alle 24 Stunden werden die Erge...