Donnerstag, 26. April 2018

SQLite oder CSV auf Raspberry Pi 2/3 mit Win 10 IoT


Irgendwann kommt der Punkt, da möchte man seine Daten auch speichern. Bei dem Einsatz von vielen Daten kann auf die Klassische Art in einer CSV im IsolatedStorage gespeichert werden. Ist einfach zu lesen führt aber zu redundante Dateninhalte. Mit SQLite lassen sich relationale Dateninhalte zusammenstellen. Aber man muss zusätzliche Referenzen hinzufügen und sich mit SQL auseinandersetzen (allerdings nur ein wenig). Beide Varianten funktionieren auf PC, Tablet, Windows Phone und natürlich auf Raspberry Pi 2 und 3.

Was nehme ich?
Vorweg sollte man sich fragen, was wird mein Ziel. Das hängt immer von der eigenen Anwendung ab. Soll in der Stunde ein Durchschnittswert errechnet werden der dann angezeigt werden soll, dann reicht sicherlich ein Array.
Möchte ich Benutzereinstellungen Speichern? Dann könnte der folgende Programmschnipsel reichen der den IsolatedStorage verwendet.

ApplicationData.Current.LocalSettings.Values["MyKey"] = MyValue;
if (ApplicationData.Current.LocalSettings.Values.ContainsKey("MyKey"))
   
MyValue = ApplicationData.Current.LocalSettings.Values["MyKey"];

Aus <https://stackoverflow.com/questions/42750736/uwp-how-to-use-isolated-storage>

Für diesen Bleiben wir zunächst bei einfachen Datensätzen, die einfach hintereinander gespeichert und in einer Tabelle abgebildet werden können. Der Teil für Relationale Datenhaltung wird auf den nächsten Post eingegangen.

Datenabruf
Der Vorteil einer Datenbank Abfrage ergibt sich dann beim Abrufen der Daten. Wurden z.B. so viele Daten gespeichert, dass es in Summe ca. 20 Megabyte groß ist, dann könnte sich das Öffnen der CSV Datei etwas lang werden. Und wenn man nur zu einem Bestimmten Zeitraum oder einen Bereich haben möchte, dann ist das gesamte einlesen einer Datei und das Parsen der Inhalte relative Zeitintensiv.

Performance Vergleich?
Hierzu muss man sagen, dass der IsolatedStorage nicht gedacht ist, schnell einzelne Daten zu speichern. In der Demo Anwendung sind zwei provisorische Klasse mit IsolatedStorage angelegt. Diese sind nur soweit geschrieben, das damit Lesen, Speichern und zurück setzen ermöglicht. (siehe am Ende Link zum Github Repository). Letzten Endes sollte anhand der Ergebnisse zeigen, was für den einen oder anderen die bessere Entscheidung sein könnte und unabhängig von weiteren Kriterien betrachtet werden sollte.

Test Vorgang
Als erstes wird festgelegt, wie viele Daten geschrieben werden. Vorbelegt als Default Anzahl sind zehn Datensätze zu erstellen. Die Werte sind in allen Datensätzen dieselben, außer der letzte wird abwechselnd zu gewiesen (hier Wohnzimmer und Flur).


Jeder Vorgang wird zehnmal ausgeführt und gemessen. Daraus ergibt sich dann die Durschnittszeit.

  • Bestehende Daten löschen
  • Zehn Mal die Anzahl Daten schreiben. Bei jeden neu lauf, werden die Daten wieder gelöscht.
  • Die Zehn Daten, zehnmal lesen. Daten werden immer neu geladen.
  • Datenabrufen und auf "Wohnzimmer" filtern. Auch hier werden die Daten immer neu geladen

 Nach dem Durchlauf werden die Ergebnisse der einzelnen Durchläufe sowie der Durchschnittswert angezeigt.

Beispiel Anwendung
Die Beispiel UWP Anwendung ist nur Zweckmäßig für den Test aufgebaut. Oben Links kann über die Textbox ein Wert ab 1 eingetragen werden und setzt damit die Anzahl Daten, die geschrieben werden sollen. Mit dem Button "Run" wird der Test Ausgeführt. Sobald dieser durchlaufen ist, steht unter dem Button "Finish".
Rechts sind zwei Textboxen die wiederum zwei Beispiele zu SQLite und IsolateStorage abbilden, die im Programmcode im Einzelnen betrachtet werden können. (Die Ergebnisse im Bild können von PC zu PC abweichen)


Ergebnisse
Ziel Systeme sind natürlich der Raspberry Pi 2 und 3. Warum das Schreiben mit SQLite auf dem Raspberry Pi3 langsamer ist, konnte ich mit dem Testaufbau noch nicht ermittelt. Zudem kommt, dass nach dem Aufräumen der Methoden Inhalte, sich die Zeiten verschlechtert haben. Warum dies ist, werde ich auf einen späteren Post eingehen.

Ergebnisse in dem der Programmcode einfach runter geschrieben wurde


Ergebnisse nach dem Aufräumen


Fazit
Der Aufbau der Methoden kann sicherlich besser gelöst werden. Die Methoden Inhalte in 'Func<T>' auszulagern ließ die Ergebnisse deutlich mehr schwanken. Die Gemessene Zeit stieg um das Sieben- bis Zehnfache an beim Lesen mit 'Where' Abfragen. Der Test mit Schreiben in die SQLite Datenbank ist dahingegen weniger abweichend, dafür aber beim IsolatedStorage ist die Durschnittszeit auf ca. das zwanzigfache angestiegen.
Beide haben Vor- und Nachteile. Mit diesen Beispiel Test wäre IsolatedStorage gut fürs schnelle Speichern und für das schnelle Lesen die SQLite Datenbank.
Die Auslegung der Tests ist sehr spärlich und behandelt noch nicht das Verwenden von Relationalen Daten. Das kommt dann mit den nächsten Posts.

Offene Punkte:
  • Speichern von Relationalen Daten
  • Abruf von Relationalen Daten
  • Probleme mit Code Optimierung




Referenzen

Dienstag, 10. April 2018

Gamepad per UWP


Hätte ich ein wenig mehr Recherchiert, dann hätte ich gleich eine UWP Anwendung geschrieben. Mit Visual Studio kann man schon seit Windows 8.1 UWP Anwendungen schreiben, die für PC, Tablet und Windows Phone funktionieren. Klar, Windows Phone ist vielleicht nicht mehr interessant, aber man kann das Smartphone immer noch zum Basteln verwenden.

Suche nach bestehenden Lösungen
Vor einigen Wochen suchte ich eine Lösung, wie man andere GameController einlesen kann und stieß auf einen anderen Blog Namens "Sander van de Velde", der über die Klasse Gamepad in UWP Anwendung schrieb. Kurz gelesen und gleich probiert und siehe da, es geht. Man kann sogar mit dem Xbox Controller die eigene Anwendung Teilweise sogar Bedienen. Egal ob auf PC, Tablet oder Raspberry Pi. 


Für den Einstieg
Als Beispiel wird ein Punkt auf der Anwendung bewegt. Der Aufbau zeigt, welche Grundlegende Programmcodezeilen Notwendig sind, um die Eingabe vom Xbox Controller zu nutzen.

Gamepad erkennen
Wenn der Xbox Controller mit dem Ziel Gerät verbunden wird, dann wird das GamepadAdded Event gesendet. Das gleiche gilt auch wenn der Controller getrennt wird (GamepadRemoved). Hinweis: MainViewModel habe ich nicht aufgeführt (siehe ggf. Github).

 // ist eigentlich bei einem so kleinen Beispiel nicht Notwendig ein ViewModel zu verwenden 
 private MainViewModel _viewModel = new MainViewModel();  
 // Ermöglicht eine Schleife Asynchron zum Haupteil der Anwendung auszuführen.  
 private Task _run;  
 // Hauptklasse für das einlesen des Xbox Controllers.  
 private Gamepad _gamePad;  
 // Wird nur für den zufälligen Farbwechsel verwendet.  
 private Random _random = new Random();  
 public MainPage()  
 {  
   this.InitializeComponent();  
   this.DataContext = this._viewModel;  
   Gamepad.GamepadAdded += this.Gamepad_GamepadAdded;  
   Gamepad.GamepadRemoved += this.Gamepad_GamepadRemoved;  
   this.MovingPoint.Fill = new SolidColorBrush(Colors.Black);  
 }  
 // Wenn der Xbox Controller getrennt wird.  
 private void Gamepad_GamepadRemoved(object sender, Gamepad e) =&gt; this._gamePad = null;  
 // Wird ausgeführt, wenn ein Xbox Controller verbunden wurde.  
 private void Gamepad_GamepadAdded(object sender, Gamepad e)  
 {  
   this._gamePad = e;  
   // … Ab hier kann der Controller ausgelesen werden.  
 }  

Gamepad einlesen und Eingabe Werte verwenden
Das Auslesen gibt ein Typ von GamepadReading zurück. Dieser enthält alle Eingaben, die zu diesen Zeitpunkt des Auslesen getätigt wurden.
Die UI und das Auslesen befinden sich in zwei Ebenen und laufen somit Asynchrone zu einander. Damit die umgerechneten Werten an die UI Elemente zugewiesen werden können, wird der Dispatcher verwendet.

 // Liest die den Xbox Controller aus. Für den Vorgang wird eine Schleife verwendet.  
 private async void ReadingGamepad()  
 {  
   while (true)  
   {  
     if(this._gamePad == null) { break; }  
     // Aktuelle Eingabe erfassen.  
     GamepadReading gp = this._gamePad.GetCurrentReading();  
     // UI und der Task laufen in verschiedenen Ebenen.   
     // Daher muss über den Dispatcher die Eingabe Daten   
     // und die Daten aus dem ViewModel Bindung Async behandelt werden.  
     await this.Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.High, () =>  
     {  
       // Linken Stick einlesen  
       var left = gp.LeftThumbstickX;  
       var top = gp.LeftThumbstickY * -1;  
       // Abstand zum Rand  
       var distanceToBorder = 30;  
       // Eingabe zu einer Position auf der Anwendung umrechnen.  
       var halfLeft = (this.ActualWidth - (distanceToBorder * 2)) / 2d;  
       left = halfLeft + (left * halfLeft);  
       var halfTop = (this.ActualHeight - distanceToBorder * 2) / 2d;  
       top = halfTop + (top * halfTop);  
       // verhindern, dass der Punkt den Rand nicht erreicht.  
       if (left < distanceToBorder) { left = distanceToBorder; }  
       if (left > this.ActualWidth) { left = this.ActualWidth; }  
       if (top < distanceToBorder) { top = distanceToBorder; }  
       if (top > this.ActualHeight - distanceToBorder) { top = this.ActualHeight - distanceToBorder; }  
       // Position als Text Ausgeben mit bis zu zwei Stellen hinter dem Komma.  
       this._viewModel.TextPosition = $"LEFT: {left:N2}, TOP: {top:N2}";  
       // leider kein Binding, daher die simple zuweisung der Ziel Position und Farbe  
       this.MovingPoint.Margin = new Thickness(left, top, 0, 0);  
       // Farbe wechseln  
       if (gp.Buttons == GamepadButtons.A)  
       {  
         // verkürzte die schreibweise  
         byte f() => (byte)this._random.Next(0, 255);  
         this.MovingPoint.Fill = new SolidColorBrush(Color.FromArgb(255, f(), f(), f()));  
       }  
     });  
     // 2 Millisekunden interval  
     await Task.Delay(2);  
   }  
 }  

Fazit
Das war alles. Und dazu war keine zusätzliche GameEngine Notwendig, um die Eingabedaten vom Xbox Controller zu lesen. Auf dem PC und Tablet funktioniert dies auch Kabellos. Nur mit dem Raspberry Pi 2 und 3 konnte ich die Verbindung nur mit einem USB Micro herstellen. Auf Windows Phone (Lumia 930) ließ sich die Anwendung starten, aber es konnte keine Bluetooth Verbindung hergestellt werden.

Quadrupes Simulation nochmal von vorne

  Die erste Version von der Simulation hatte ich mit Claude Sonnet 5.5 generiert und die war in Ordnung, aber nicht das was ich mir eige...