Sonntag, 24. Mai 2020

Angular auf dem Raspberry Pi


Zuvor hatte ich den Raspberry Pi nur für Windows 10 IoT mit UWP Entwicklung gesehen. Das lag mit unter daran, dass ich nicht viel Zeit hatte, um mir anderes technologisches Wissen anzueignen. Doch gerade habe ich auf Grund der aktuelle Lage  um Corona und in Kurzarbeit habe ich  Zeit mich mit anderen Technologien zu beschäftigen. Darunter fiel auch das Front-End Webapplikationsframework Angular.

Bei Windows 10 IoT auf dem Raspberry Pi beschränkte es sich leider nur auf eine aktiv laufende Anwendung, was dazu führte eine monolithische Anwendungen zu schreiben. Und zum derzeitigen Zeitpunkt  gibt's auf Windows 10 IoT und Docker nur für x86/x64 Systemen, also leider noch ein Grund, diese Kombination aufzugeben.

 

Mit etwas Weitblick, fand ich heraus, dass Angular auch in einem Docker Container laufen kann und somit dann auch auf einem Raspberry Pi.

 

Voraussetzung

  • Raspberry Pi 2, 3 oder 4
  • Betriebssystem Raspbian (geht auch mit anderen Linux Betriebssystem für Raspberry Pi)
  • Docker Account

 

Anforderung

Eine Angular Hello World Anwendung in einem Docker Container starten auf einem Raspberry Pi.

 

Hello Angular Image

Zunächst brauchen wir eine Angular Projekt. Wenn ihr bereits eins habt, verwendet es einfach. Das Einzige, was hinzugefügt wird, ist die Datei ‚Dockerfile‘ im Root Verzeichnis des Projektes. In meinem Beispiel hat das Projekt den Namen (das auch der Namen, den das Root-Verzeichnis trägt) "ExampleHelloDocker", der in der Dockerfile auch so stehen sollte.

 

 FROM arm32v7/alpine  
 COPY /dist/ExampleHelloDocker /usr/share/nginx/html  
 EXPOSE 80  

 

Nun brauchen wir die Eingabeaufforderung oder das Terminal aus Visual Studio Code. Wechselt dazu in das Projekt Root Verzeichnis und erstellt ein Angular Build.

 

 ng build --prod  

 

Und im Anschluss das Docker Image erstellen. Da hier keine Großbuchstaben verwendet werden, habe ich für die Lesbarkeit Bindestriche eingesetzt.

 

 docker build -t example-hello-docker .  

 

Taggen und pushen

Im nächsten Schritt wird vor dem Pushen auf Docker-Hub, das Image getagt. Dazu braucht ihr euren Benutzernamen von Docker.   

 

docker tag example-hello-docker:latest <username>/example-hello-docker:latest

 

Anschließend kann es nun auf Docker-Hub hochgeladen werden.

 

 docker push <username>/example-hello-docker:latest  

 

Fertig. Nun sollte euer Image auf Docker-Hub zu sehen sein. Falls ihr euer Image nicht öffentlich bereit stellen wollt, sollte ihr auf Docker-Hub das Image im Repositories auf private umstellen.

 


 

Image ziehen und starten

Ab hier sollte das ziehen und starten wie bei jeder anderen Docker Image laufen.

 

 docker pull <username>/example-hello-docker:latest  


Um die Docker Image ID zu erhalten, schauen wir, ob das Image nun vorliegt.

 

 docker image ls  

 

 

Und nun heißt es endlich starten!!
Portnummer 5000 ist die, unter der die Angular Anwendung vom Netzwerk aus zu erreichbar ist.
Hier könnt ihr auch eine andere Portnummer festlegen.

 

 docker run --publish 5000:80 -t <image id>  

 

Oder besser, wenn man den Raspberry pi wieder einschaltet, auch der Container weiter läuft.

 

 docker run --publish 5000:80 -t --restart always <image id>  

 

Lokal auf dem Raspberry Pi sollte nun die Angular Webanwendung im Browser mit der Adresse http://localhost:5000 erreichbar sein. Und natürlich über das Netzwerk von einem anderen Rechner mit der IP Adresse und dem gesetzten Port.



 

HINWEIS: Seit Januar 2021 musste ich feststellen, dass das erstellen des Docker Image von einem Windows System in der Form wie beschrieben nicht funktionieren könnte. Alternativ kann die gebaute Angular Anwendung auf Rasbian kopiert werden und dort mit dem Docker Build Befehle das Image erstellt werden.


Das Thema ist bereits mehrmals auf Stackoverflow vorhanden, aber wenig detailiert. Deshalb habe ich selbst einen Eintrag geschrieben, dass praktisch die Beschreibung dieses Blog-Post in Kurzform und auf Englisch wiedergibt. 

Can't create docker image for raspberry pi with angular app

Sonntag, 19. April 2020

GO(lang) on Raspberry Pi


Kleine Anwendungen können ganz gut auch auf kleinen Systemen laufen. Da ich mich in letzter Zeit öfter mit GO (Golang) auseinandersetze, wende ich mich ein wenig von Windows ab und schaue seit längerer Zeit mal wieder über den Tellerrand. Hängen geblieben bin ich in den verfügbaren Linux Betriebssysteme für Raspberry PI und dem Einsatz von Docker.

Mit Docker kann man alles machen. Naja, nicht alles alles. Mein Kaffee muss ich immer noch selber kochen, außer ich schreibe eine Anwendung und baue einen Treiber mit Sensoren und Aktoren, um die Kaffeemaschine zu kontrollieren. Dann sollte das auch mit Docker gehen. Oder ich schicke der Frau eine Nachricht und bitte um Kaffeenachschub. Aber fangen wir mit etwas einfacherem an.

Wir brauchen dazu:

Anforderung
Am Ende soll auf dem Raspberry Pi ein Webservice laufen, der in einem Docker Container gestartet wird. Der Dienst soll von einem anderen PC mit einem Browser im lokalen Netzwerk aufrufbar sein und schließlich eine Testwebseite wiedergeben.

Webservice entwickeln in GO
Einen Webservice in GO zu schreiben ist sehr übersichtlich. In C# mit .NET Framework habe ich deutlich mehr Codes geschrieben, bis endlich ein Webservice lief. Zudem fällt mir das Wegdenken von Programmiergewohnheiten schwer, die eigentlich nicht sein müssen. Man spricht auch oft von Boilerplate-Code.

Kommen wir zu dem GO Code mit dem geschriebenen Webservice. Nachdem wir im Projektordner eine neue Datei angelegt haben mit dem Namen 'main.go', geht das Schreiben mit dem Code los, bzw. einfach den folgenden Code nach dem Absatz kopieren.
Im Detail zu der Sprache GO kann ich die Seite von Golang 'A Tour of Go' empfehlen. Dort lernt ihr alle Grundlagen zu der Sprache und könnt die Eingaben direkt im Browser prüfen.
In der 'main()'-Funktion startet die Anwendung den Webservice. Kommt ein URL Request rein, wird durch die Handler Function ‚http.handleFunc‘ der Delegat ‚webserviceHandler‘ ausgeführt. Dieser kann den eingehenden Request verarbeiten. Doch für dieses Beispiel kommen wir ohne Parameter aus. Stattdessen wird eine HTML Seite eingelesen und als Response zurückgegeben.

 package main  
 import (  
      "fmt"  
      "io/ioutil"  
      "net/http"  
 )  
 func main() {  
      fmt.Println("start webservice")  
      http.HandleFunc("/", webserviceHandler)  
      http.ListenAndServe(":5000", nil)  
 }  
 func webserviceHandler(w http.ResponseWriter, r *http.Request) {  
      webside, err := ioutil.ReadFile("index.html")  
      if err != nil {  
           fmt.Println(err)  
           return  
      }  
      fmt.Fprintf(w, string(webside))  
 } 




Lokal den Service ausprobieren
An der Stelle sollte der Code bereits ausführbar sein. Wenn jetzt F5 zum Ausführen gedrückt wird, dann sollte Lokal mit einem Browser der Webservice mit der Adresse 'http://localhost:5000' aufrufbar und das ‚Hello World Webseite‘ zu lesen sein.

Dockerfile anlegen
Damit der Webservice auch im Docker Container funktioniert, ist relativ wenig zu tun. Relativ, an der Stelle habe ich lange gesucht und mich lange mit Docker auseinandergesetzt, um am Ende die einfach aussehende Dockerfile zu schreiben. An der Stelle kann ich nur empfehlen, die einzelnen Befehle nach zu lesen, die für die Dockerfile verwendet werden können. Dockerfile Preference https://docs.docker.com/engine/reference/builder/
Die Dockerfile kommt in dasselbe Verzeichnis, wie die Datei mit dem GO Code. Die Einträge mit 'LABEL' sind optional und können auch weggelassen werden.

 # Initialisiert die Bereitstellung und   
 # legt das Basis Image mit nachfolgenden Anweisungen an  
 FROM golang:onbuild  
 # label of maintainer  
 LABEL maintainer="Codexzier"  
 LABEL version="1.0"  
 LABEL description="A mini messenger server with based user."  
 # legt das Verzeichnis an für die Ausführung  
 WORKDIR /app  
 # kopiert die neuen dateien in den container  
 COPY . .  
 # Führt das Image aus und übergibt das Ergebnis für die Zielumgebung  
 RUN env GOOS=linux GOARCH=arm GOARM=5 go build -o main .  
 # Freigabe der Portnummer auf das der docker container zuhört  
 EXPOSE 8002  
 # Anweisung für den Ausführenden Container  
 CMD ["./main"]  

Docker Image erstellen
Ab dieser Stelle sollte Docker installiert und gestartet sein. Ihr solltet auch einen Account haben, damit das Image anschließend auf Dockerhub geladen werden kann.
In Visual Studio Code öffnen wir zunächst ein Terminal. Hier sollte aktuell das Verzeichnis aufgerufen sein, worin sich der Programmcode, Webseite und die Dockerfile befinden. Hier gibt ihr folgenden Command ein und am Ende den Punkt nicht vergessen:

docker build -t <image name> .




Nun sollte eine Folge von Arbeitsschritten erfolgen bis dann am Ende Successfully build mit einer kryptischen ID steht und ggf. mit einigen Warnhinweisen.





Bevor auf Dockerhub geladen werden kann, muss noch das Image tagged gesetzt werden (Image tagged, da bin ich nicht sicher ob man das so beschreiben kann). Statt wie bei mir mit Codexzier, müsst ihr euren Docker Username verwenden.

docker tag <tag name>:latest <docker username>/<ziel name>:latest





Nun kann das Image auf Dockerhub hochgeladen werden. Auch hier verwendet ihr euren Username von Docker.

docker push < docker username >/< ziel name >:latest




Nach ein paar Sekunden sollte der Upload abgeschlossen sein. Wenn ihr euch auf Dockerhub https://hub.docker.com/ eingeloggt habt, sollte euer Image in euren Repositories zu finden sein.




Das Image steht bereit und kann vom Zielsystem abgeholt werden. Auf Dockerhub könnt ihr weitere Informationen zu eurem Image hinterlegen. Zum Beispiel, was der Inhalt ist und welche Aufgabe die Anwendung erfüllt. Wenn ihr wollt, dass nur ihr das Image sieht, dann kann hier auch auf Private umgestellt werden. Interessant dürfte auch das Automated Builds sein, aber das wäre wieder ein eigenes Thema.

Docker auf dem Raspberry Pi
Das Beispiel zeigt den Einsatz von Docker mit dem Betriebssystem Raspbian. Von Haus aus ist Docker nicht installiert, ebenso wenig wie Docker-Compose. Im Netz finden sich einige Beschreibungen, daher habe ich den Part hier ausgelassen.

Hinweis: Bei mir muss ich kein "sudo" vor jeden command eingeben.

Um nun das Image herunter zu laden, muss folgender Command ausgeführt werden:

docker pull <docker username>/<image name>




Nach dem Download wird das Image mit einer Image-ID angelegt. Die brauchen wir, um die Anwendung zu starten. Mit folgendem Befehl bekommt ihr alle vorhandenen Images ausgeben.

docker image ls

In der Spalte zu IMAGE ID steht, die ID zu eurem heruntergeladenen Image. Die hier angezeigte ID wird Lokal erzeugt und sollte eine andere ID abbilden.
Mit dem nächsten Command, setzt ihr die entsprechende ID am Ende ein:

docker run --publish < port number to pulish >:< internal port number > -t < image id >



Die gewählte Port 8003 im Beispiel kann frei gewählt werden, wenn die Portnummer nicht von einer anderen Anwendung bereits verwendet wird. Die IP Adresse von Raspberry könnte ihr unter Raspbian auslesen, in dem ihr mit der Maus über das Netzwerk Symbol fährt. Mit der von eurem Raspberry Pi IP Adresse und der gesetzten Portnummer, solltet ihr auf eurem PC, Notebook und Smartphone auf den Raspberry zugreifen können und die Webseite 'Hello World!' erhalten.


Langer Weg für die Bereitstellung
Die Bereitstellung ist etwas lang, aber durch die Docker-Umgebung auf eine einheitliche Ebene gebracht. Das Gleiche sollte nicht nur mit GO funktionieren, sondern auch mit anderen Sprachen. Und Dank Docker können wir einen Service schreiben, der praktisch auf allen Systemen bereitgestellt werden kann.

Wie immer ist der fertige Code auf meinem Repository verfügbar

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.


Dienstag, 27. August 2019

Gästebuch mit Wemos


Als ich mit meiner Freundin zum Geocaching unterwegs war, fiel mir ein das ich ein Programm schreiben wollte, das die Funktion eines Gästebuch ausführt und das auf einem Mikrocontroller. Wer Geocaching kennt, der weis dass man nach einem gefundenen Geocach sich auf einem beiliegenden Zettel eintragen kann. Und genau das will ich als digitale Form abbilden.

Was brauche ich:
  • Wemos
  • SD Shield
  • SD Karte

Anforderung
Für ein Gästebuch, soll ein (Nick)Name und Datum hinterlegt werden. Datum wird Automatisch mit dem Eintrag gesetzt, so das nur der Nickname eingetragen wird. Hier sollte der Wemos als eigenständiger Access-Point funktionieren, so das man sich mit dem Smartphone verbinden kann. Auf der Webseite sollten zwei Textboxen für die Eingabe des Namens und Datum vorhanden sein. Das Speichern erfolgt dann über ein Button. Neben der Eintrage Möglichkeit, soll auch eine Tabelle mit bereits bestehenden Einträgen zusehen sein. Und natürlich sollte der Zugang per Smartphone funktionieren.

Webseite schreiben
Leider kann die Webseite nicht mit Bootstrap aufgebaut werden. Na ja, eigentlich schon, aber das wäre ein eigenes Thema. Diesmal wird die Seite wieder Klassisch und einfach aufgebaut nach Standard HTML Bordmitteln.
Die Webseite wird mit in dem Programmcode eingebettet, so das diese nicht von der SD Karte erst gelesen werden muss. Zudem sollte bei defekter SD Karte trotzdem etwas angezeigt werden, wie eine Fehlermeldung.
Da die Darstellung des Code mit HTML auf dem Blog sich schwierig gestaltet, kann der Programmcode für den Seitenaufbau auf dem Github Repository betrachtet werden [Link zu WebsiteContent].
Die Seite habe ich zuvor ganz normal in HTML geschrieben und das Ergebnis im Browser immer wieder betrachtet. Erst danach kam die Seite in den Programmcode, das jedoch optisch grausig aussieht. Daher habe ich den Teil in einen eigenen Tab bzw. Code-Datei verfrachtet. Am Ende kam fast das selbe Ergebnis wie in den Folgendem Bild zu sehen ist.



Das Aktuelle Datum wird durch ComboBoxen ermöglicht, in dem man das aktuelle Datum und Uhrzeit einrichten kann. Das testen auf dem Windows Phone erfolgt ohne Probleme. Auf dem Iphone jedoch nicht.








SD Karte prüfen
Mit dem Start soll zunähst geprüft werden, ob die SD Karte verfügbar ist. Zuerst wird jedoch geprüft, ob bereits die Datei mit den Gästeeinträgen vorhanden ist. Wenn nicht, dann wird diese angelegt.

 void InitSdCard() {  
  SD.begin(mChipSelect);  
  if(!SD.exists(mGuestList)){  
   Serial.println("Create file...");  
   File dataFile = SD.open(mGuestList, FILE_WRITE);  
   dataFile.close();  
   Serial.println("...finish");  
  }  
  else {  
   Serial.println("the guest list exist");  
  }  
  if(!mCard.init(SPI_HALF_SPEED, mChipSelect)) {     // initialize sd card and check insert sd card  
   Serial.println("XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX");  
   Serial.println("\t\tERROR: can not init sd card");  
   Serial.println("XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX");  
   return;  
  }  
  if(!mVolume.init(mCard)) {               // initialize sd card  
   Serial.println("XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX");  
   Serial.println("\t\tERROR: Could not find FAT16/FAT32 partition.\nMake sure you've formatted the card");  
   Serial.println("XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX");  
   return;  
  }  
  Serial.println("SD Card: Exist and partion was found");  
 } 

Wemos zum Access Point machen
Auf der Webseite von Wemos ist es eigentlich schon beschrieben, aber zur Vollständigkeit, nehme ich das hier denoch auf. Um den Wemos zum Access Point zu machen, bedarf dies nur weniger Code zeilen. In den Globalen Variablen muss dann nur die Instanz vom WiFiServer gehalten werden. Für den Zugang wird kein Passwort festgelegt.

 void StartWebserver() {  
  Serial.println("Start access point");  
  WiFi.mode(WIFI_AP);            
  byte setMode = 0;               // 0 = disable, 1 = enable router information  
  wifi_softap_set_dhcps_offer_option(OFFER_ROUTER, &setMode);  
  WiFi.softAP("VisitorsBook", "");  
  mServer.begin();                // Start the HTTP Server  
  Serial.print("Ip Address: ");  
  Serial.println(WiFi.softAPIP());// Send the IP address of the ESP8266 to the computer  
 } 

Aufruf der Seite
Mit dem Aufruf der Seite wird zunächst geprüft, ob der Benutzer einen Request sendet. Wenn nicht, wird die Methode zum Speichern umgangen. Den Request nach dem Inhalt zuprüfen, sieht hier etwas kryptisch aus, das jedoch nur schlimm aussieht. Beim auslesen des Datums wird ein Doppelpunkt mit '%3A' ausgefüllt, der sich jedoch einfach mit einem Replace wieder in einen Doppelpunkt gesetzt werden kann. Nach dem aufbereiten des Namens und Datum kann das Ergebnis gespeichert werden.

void printOnWebside() {  
  WiFiClient webclient = mServer.available();  
  if (!webclient) {  
   return;  
  }  
  String request = webclient.readStringUntil('\r');  
  webclient.flush();  
  int indexGuest = request.indexOf("guestname=");  
  if(indexGuest != -1) {  
   int indexEndGuestname = request.indexOf('&');  
   String guestName = request.substring(indexGuest + 10, indexEndGuestname);  
   int indexSetTime = request.indexOf("settime=");  
   int indexEndSettime = request.indexOf(" HTTP");  
   String settime = request.substring(indexSetTime + 8, indexEndSettime);  
   // Example: 2019-08-25T19%3A40  
   // filter technical colon  
   settime.replace("T", " ");  
   settime.replace("%3A", ":");  
   WriteGuest(guestName, settime);  
  }  
  BuildWebside(webclient);  
 } 

Datenspeichern
Das Speichern erfolgt Zeilenweise. Hier wird der Eintrag mit einem Zeilenumbruch in die Datei hinzugefügt. Zuerst hatte ich nur Name und Datum, aber das Parsen wäre relativ aufwendiger geworden. Daher wird die Zeile bereits mit dem Ziel HTML Format eingetragen. (Wegen HTML Inhalt ist der folgende Code nur ein Bild)



















Daten abrufen
Ist etwas lang die Methode. Aber auch nur, weil wieder etwas HTML mit eingeflossen ist, um die Anzahl der gespeicherten Einträge wieder zugeben. Anschließend werden die Gast Einträge aus der Datei Zeilenweise mit dem bereits HTML Anteil abgerufen und hinzugefügt.































Offene Punkte
Das Hübsch machen der Webseite habe ich weggelassen, sowie das Fehlerbehandlung. Denn ein Eintrag kann ohne Namen und Datum erfolgen und führt dazu, dass der Eintrag in der Liste wirklich leer sein kann. Zudem war auch ein RTC Timer angedacht, der jedoch für den ersten Schritt nicht benötigt wird. Im Main Branch habe ich diesen Teil stehen gelassen.
Und dann noch der Fehler, dass das Datum nicht geht, wenn versucht wird vom Iphone ein Request zu senden.


Datenschutz
Die Anmeldung erfolgt anonym, weil hier keine Personenbezogenen Daten erhoben werden. Um genau zu sein, dürfen hier keine Personendaten erhoben werden, wie Email (Macht in diesen Kontext auch kein Sinn), Namen und Geburtsdatum. Für den Eintrag reicht dann der Hinweis:

Mit meinem Eintrag ins Gästebuch erkläre ich mich einverstanden, dass dieser Beitrag mit Datum und Uhrzeit auf der Gästebuch-Seite hier Lokal öffentlich lesbar wird.

Weitere Informationen findet ihr auf der Seite zu "Datenschutz auf Webseiten"

Wie immer das ganze Beispiel auf meinem Github Repository


Ameisen Simulation und andere Dinge

Wer lange sich schon mit C# beschäftig, hat sicherlich schon mal was von AntMe gelesen oder gehört. Diese Idee hatte ich aufgegriffen und mi...