Dienstag, 2. Februar 2021

Balkendiagramm mit WPF


Seit letztem Jahr habe ich an meinem kleinen Programm gebastelt, mit dem man die RKI Daten sichten kann.

Diesmal habe ich die Darstellung eines Balkendiagramms gewählt, welches mit wenigen Schritten realisierbar ist.

  

Anforderung

Ein neues Steuerelement soll so erstellt werden, dass es im eigenen Projekt immer wieder verwendet werden kann. Hier soll eine Liste mit Werten möglich sein, in das Bar Diagramm zu ‚binden‘. Die Anzahl der Elemente bestimmt die Breite des einzelnen Balken und der Wert wiederum die Höhe.

 

Neues Steuerelement anlegen

Das Anlegen des neue Steuerelements sollte in einem eigenen Order erfolgen. Mit rechter Maustaste und dann im Kontext-Menü Add -> User Control (WPF)… beginnen wir mit dem Benutzersteuerelement.



Gebt einen Namen für das Steuerelement ein,  hier empfehle ich am Ende Control hinzuzufügen. In einigen Projekten werden z.B. benutzerdefinierte Steuerelemente und Ansichten (Views) unterschieden.


 

Nach dem Erstellen sollte unter der .XAML- auch eine .XAML.cs-Datei bestehen. In diese werden die Dependency Properties hinzugefügt, mit dem es später möglich ist, die Liste mit Werten zu ‚binden‘.

 


Zunächst brauchen wir im XAML Code ein StackPanel, in dem die Werte als Balken abgebildet werden. Idealerweise kann das Grid gegen ein Border Element ausgetauscht werden. Auch ein Rand in der Farbe eurer Wahl sollte nicht fehlen. Früher oder später stellt man fest, dass der Rand zur Orientierung hilft, das Diagramm in seinen Grenzen zu lesen.

Das StackPanel wird direkt in das Border Element untergeordnet. Hier muss die Ausrichtung der Orientierung und die Flow Direction von links nach rechts festgelegt werden. Da die Balken von links beginnen, muss das Ausrichten horizontal nach links ausgerichtet sein. Zur Erleichterung bekommt das StackPanel einen Namen zugewiesen, der über den Codeabschnitt verwendet wird. Das erleichtert das Hinzufügen der Balken.

 <UserControl x:Class="ExampleBarDiagram.Diagram.BarDiagramControl"  
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"  
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"  
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"   
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"   
        mc:Ignorable="d"   
        d:DesignHeight="450" d:DesignWidth="800"  
        SizeChanged="UserControl_SizeChanged">  
   <Border BorderThickness="1"   
       BorderBrush="Chartreuse"   
       Margin="0">  
     <StackPanel x:Name="SimpleDiagram"   
             Orientation="Horizontal"   
             FlowDirection="LeftToRight"   
             HorizontalAlignment="Left" />  
   </Border>  
 </UserControl>  

Für die Liste mit dem Werten empfiehlt sich das Anlegen eines Datenobjektes mit dem Namen DiagramLevelItem. Dieser kann zum einen den double Wert sowie einen Text aufnehmen. Neben dem Anlegen der Property wird die Static readonly Dependency angelegt und ist nur für diese Klasse gültig. In der PropertyMetadata wird eine neue Liste erstellt, falls diese nicht zugewiesen wurde, aber versucht wird diese abzurufen. Mit dem zweiten Parameter wird eine Static Methode übergeben, die ausgeführt wird, wenn sich der Wert der Property geändert hat.

   public partial class BarDiagramControl  
   {  
     public List<DiagramLevelItem> DiagramLevelItemsSource  
     {  
       get => (List<DiagramLevelItem>)this.GetValue(DiagramLevelItemsSourceProperty);  
       set => this.SetValue(DiagramLevelItemsSourceProperty, value);  
     }  
     public static readonly DependencyProperty DiagramLevelItemsSourceProperty =  
       DependencyProperty.RegisterAttached("DiagramLevelItemsSource",  
         typeof(List<DiagramLevelItem>),  
         typeof(BarDiagramControl),  
         new PropertyMetadata(new List<DiagramLevelItem>(), UpdateDiagram));  
     private static void UpdateDiagram(DependencyObject d, DependencyPropertyChangedEventArgs e)  
     {  
       if (d is BarDiagramControl control)  
       {  
         SetValuesToBarDiagram(control);  
       }  
     }  
 ... 

Der Hauptteil für das Ausfüllen des StackPanel wird schließlich mit der Methode SetValuesToBarDiagram(BarDiagramControl control) ermöglicht. Hier wird für die aktuelle Höhe die Skalierung der einzelnen Werte in Balken verwendet, damit die Balken sich an die verwendete Größe des Steuerelements anpassen.

Die einzelnen Balken sind schlicht aus Rectangles erstellt,  die mit einer Farbe gefüllt sind. Ausgerichtet sind sie immer nach unten und die Höhe wird aus dem skalierten Wert gesetzt. Die Breite selbst wird in Abhängigkeit der Breite des Steuerelementes und der Anzahl der Werte festgelegt.

Mit dem gegeben Namen des StackPanels, kann über die Property Children das neue Rectangle Instanz hinzugefügt werden.

 ...  
    private static void SetValuesToBarDiagram(BarDiagramControl control)  
     {  
       if (control.DiagramLevelItemsSource == null)  
       {  
         return;  
       }  
       control.SimpleDiagram.Children.Clear();  
       var heightScale = control.ActualHeight / 100d;  
       var widthPerResult = control.ActualWidth / control.DiagramLevelItemsSource.Count;  
       foreach (var diagramLevelItem in control.DiagramLevelItemsSource)  
       {  
         var heightValue = diagramLevelItem.Value * heightScale;  
         var barItem = new Rectangle  
         {  
           Fill = new SolidColorBrush(Colors.DarkGreen),  
           VerticalAlignment = VerticalAlignment.Bottom,  
           Width = widthPerResult,  
           Height = heightValue,  
           ToolTip = diagramLevelItem.ToolTipText  
         };  
         control.SimpleDiagram.Children.Add(barItem);  
       }  
     }  
 ...   

Damit die Balken beim Verändern der Größe des Steuerelementes angepasst werden, wird auf das Event SizeChanged zurückgegriffen. Ab hier wird dann auch sichtbar, warum der Codeanteil separat in einer Methode aufgeführt wurde.

 ...  
     private void UserControl_SizeChanged(object sender, SizeChangedEventArgs e) => SetValuesToBarDiagram(this);  
   }   


Fast fertig

Im Grunde ist das neue Steuerelement - Diagram fertig und kann im Projekt eingesetzt werden. In diesem Beispiel wird das Steuerelement in das MainWindow.xaml hinzugefügt. Das Projekt muss einmal gebaut werden, damit das Steuerelement über die Toolbox, per klicken und ziehen in die Zielansicht gezogen werden kann.

 

Im Beispiel wurde für MainWindow.xaml ein ViewModel angelegt, dass das Interface von INotifyPeropertyChanged implementiert. Neben der Liste mit den Werten (DiagramLevelItem) kommt eine Property CommandCreateNewListWithValues mit dem Interface ICommand.

public class MainWindowViewModel : INotifyPropertyChanged  
 {  
   private List<DiagramLevelItem> _items;  
   private ICommand _commandCreateNewListWithValues;  
   public List<DiagramLevelItem> Items  
   {  
     get => this._items;  
     set  
     {  
       if (!Equals(value, this._items))  
       {  
         this._items = value;  
         this.OnPropertyChanged();  
       }  
     }  
   }  
   public ICommand CommandCreateNewListWithValues  
   {  
     get => this._commandCreateNewListWithValues;  
     set  
     {  
       if (Equals(value, this._commandCreateNewListWithValues)) return;  
       this._commandCreateNewListWithValues = value;  
       this.OnPropertyChanged();  
     }  
   }  
   public event PropertyChangedEventHandler PropertyChanged;  
   [NotifyPropertyChangedInvocator]  
   protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)  
   {  
     this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));  
   }  
 }  

Das ViewModel muss zuerst in den Window.DataContext eingetragen werden. Nun kann die Liste mit dem Diagram über die DependencyProperty DiagramLevelItemsSource gebunden werden. Als zweites kommt noch ein Button hinzu, der über Command die zweite Property vom Typ IComand bindet.

<Window  
     xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"  
     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"  
     xmlns:d="http://schemas.microsoft.com/expression/blend/2008"  
     xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"  
     xmlns:local="clr-namespace:ExampleBarDiagram"  
     xmlns:Diagram="clr-namespace:ExampleBarDiagram.Diagram"  
     x:Class="ExampleBarDiagram.MainWindow"  
     mc:Ignorable="d"  
     Title="Example Bar Diagram" Height="450" Width="800">  
   <Window.DataContext>  
     <local:MainWindowViewModel />  
   </Window.DataContext>  
   <Grid>  
     <Grid.RowDefinitions>  
       <RowDefinition Height="*" />  
       <RowDefinition Height="Auto" />  
     </Grid.RowDefinitions>  
     <Diagram:BarDiagramControl HorizontalAlignment="Stretch" VerticalAlignment="Stretch" DiagramLevelItemsSource="{Binding Items}"/>  
     <Button Content="create new list with values" Grid.Row="1" Command="{Binding CommandCreateNewListWithValues}" />  
   </Grid>  
 </Window>  

Über den Behind code MainWindow.xaml.cs wird im Konstruktor nun das ViewModel aus dem DataKontext abgerufen. Hier muss zum einen für den Button Command die Instanz erstellt werden und, zum anderen über dem Parameter wiederum das ViewModel übergeben werden. Hierfür erstellen wir die Klasse ButtonCommandCreateNewListWithValues.

 public partial class MainWindow : Window  
 {  
   public MainWindow()  
   {  
     this.InitializeComponent();  
     var viewModel = (MainWindowViewModel) this.DataContext;  
     viewModel.CommandCreateNewListWithValues = new ButtonCommandCreateNewListWithValues(viewModel);  
   }  
 }  

Die Klasse ButtonCommandCreateNewListWithValues implementiert das Interface IComand. Über den Konstruktor wird das übergebene ViewModel in den Private Member referenziert. Die Methode CanExecute wird eigentlich nicht verwendet und gibt da her nur true zurück.

Mit der Methode Execute wird mit der Random Klasse die Werte erzeugt. Da das Diagramm für Werte bis 100 ausgelegt ist, sollte der maximale Wert des Einzelnen auch bei 100 sein. Die Anzahl der angelegten Werte sollte sich darauf begrenzen, wie viele Pixel in der Breite für das Diagram zur Verfügung stehen. Die Liste mit den Werten wird erst am Ende zugewiesen, da sonst die PropertyChanged nicht ausgeführt wird.

 

Schlusswort

Die Ausführung ist schon sehr kompakt und lässt sich auch noch etwas kürzer fassen. Einige Stellen habe ich aus gewissen Gründen mehr ausgebaut als nötig. Denn ich will in weiteren Blog Einträgen beschreiben, wie man dieses Steuerelement weiter ausbauen kann und dies natürlich in kurzen Schritten.

Das Beispiel ist eine entkoppelte Darstellung des verwenden ‚Steuerelement‘ Diagram, welches ich bereits in einem anderem kleinen privaten Projekt verwende.

 

Wie immer habe ich die Solution auf Github hochgeladen:

Github: ExampleBarDiagram

 

Sonntag, 20. Dezember 2020

Alternativen zu den original LEGO Technic Motoren können beispielsweise selbstgemacht sein



Mit etwas Basteln kann ein passender Motortreiber gelötet werden, welches die Power-Function Motoren mit einem Microcontroller angesteuert werden könnten. Dies bedeutet etwas Aufwand, der sich zwar lohnt, aber auch eine Hürde darstellen kann, wenn man sich nur darauf konzentrieren möchte, etwas zu bauen und zu programmieren.

Die Kittenbot Micro:bit bieten da mehr, da sie nicht nur ein Mikrocontroller Board liefern, sondern auch Motoren anbieten, die mit LEGO Technic kompatible sind. Das Tolle an diesen Motoren ist, dass diese einfach mit dem PWM Signal angesteuert werden können und auch mit einem Stecksystem kommen, das in der Microcontroller Welt stark verbreitet ist. Worauf man sich jedoch einstellen muss, ist, dass die Bauform sich von den  der LEGO Technic Motoren unterscheiden.

 

Vorgestellt werden hier:

  • Motor Green und Orange
  • Motor Rot
  • Motor Grau

 

Unterschiede

Beim Kauf verwirrte mich die Bezeichnung ‚Servomotor‘. Ist zwar nicht falsch, aber auch irreführen, da man unter einem Servo immer einen beschränkten Drehbereich ausgeht. Der grüne und orange Servomotor steuern sich wie einen Motorenregler oder, anders ausgedrückt, Fahrtenregler. Beim grauen Motor ist eine Servo-Funktion erkennbar, auch, wenn diese keinen wirklichen Mittelpunkt hat. Denn dieser lässt sich mit dem Standard PWM Signal um fast 360 Grad drehen. Jedoch muss am Anfang festgelegt werden, wo die Mittelstellung liegen soll und, ohne Sensoren zum Kalibrieren, wird dies nur mit manuellen Eingaben möglich sein.

 

 

Grüner und oranger Motor (Servo, ähnliche Form)

Beide bilden dieselbe Funktion ab. Die Bezeichnung 'Servo' stimmt nur bedingt, weil sich das Ergebnis wie bei einem Servo Hack verhält und sich nicht nach links oder rechts (ca. 90 Grad) in eine Stellung bewegt, sondern weiterdreht. Es kann lediglich die Drehgeschwindigkeit reguliert werden.

Aus der Bauformreihe gibt auch eine graue blockgeformte Variante, das sich vermutlich wie die folgende beschriebene Servomotor verhält.

 

Details:

Anschluss: 3 Pins (Steuerung, VCC, GND

Betriebsspannung: 3,3V bis 6V

Stromaufnahme (max.): 200mA bis ca. 450mA (700mA)

Drehmoment: 5 Ncm oder 0,5 Kg

 


 

Grauer Motor (Blockform)

Dieser verhält sich wie ein Servo, kann aber um 360 Grad frei positioniert werden. Also je nachdem von wo man startet, könnte dort der Mittelpunkt sein. Hier fehlt eine mögliches Auslesen der Position, um den Mittelpunkt zu kalibrieren.

 

Details:

Anschluss: 3 Pins (Steuerung, VCC, GND)

Betriebsspannung: 4,8V bis 6V

Stromaufnahme: 900mA

Drehmoment: 20 Ncm oder 2 Kg


 

 

Roter Motor (Blockform)

Dieser kommt ohne Motortreiber, so dass dieser nicht mit einem PWM Signal steuerbar ist. Hier wird ein eigner Motortreiber fällig. Aus meiner Sicht kann man diesen Motor für etwas verwenden, das dauerhaft in einer Geschwindigkeit drehen soll.

 

Details:

Anschluss: 2 Pins (VCC, GND)

Betriebsspannung: 4,8V bis 6V

Stromaufnahme: 900mA

Drehmoment: 20 Ncm oder 2 Kg

 

 

 

Kompatible Bauformen

Interessant ist die Bauform, weil diese mit LEGO Technic oder anderen Klon-Produkten eingesetzt werden können.
Eigentlich ist das Kittenbot Board dazu gedacht, eigene Sets erstellen zu können. Diese sind ebenfalls mit konnektoren Löcher versehen.

 




 

 

 

Fazit

Das Kittenbot Micro:bit ist nicht sonderlich verbreitet, wie Makeblock beispielsweise. Dieses bietet auch Kompatibilität zu LEGO Technik an -scheint es aber nicht mehr zu geben.

Die Leistungsdaten sind leider sehr schwach. Ich hatte von den Servos in Blockform mehr erwartet. Servos mit ähnlicher Baugröße haben oft schon fast das doppelte an Drehmoment. Dies ist aber nicht schlimm, mit unterschiedlicher Zahnradübersetzung kann ein größeres Drehmoment erreicht werden.

 

Montag, 30. November 2020

RKI Daten einlesen und Ausgeben


Dieses Jahr ist anders. Anders, weil unser Alltag immer irgendwie mit Corona zu tun hat. Wir halten Abstand, wir tragen Masken und hoffen, dass die Pandemie in den Griff bekommen wird. Manchmal habe ich Zweifel, aber die Zweifel sind trügerisch und führen zu einem Paradoxen Gedankenweg. Statt zu denken, wir können lockerer mit den Regeln der Pandemie umgehen, weil wir hier nicht so stark betroffen sind - wie andere Teile von Europa - sollten wir daran denken, dass wir hier in Deutschland besser dran sind, da große Teile der Bevölkerung die AHA Regeln ernst nehmen und berücksichtigen.

 

Die Daten des RKI, die wir immer wieder auf verschiedenen Seiten sehen, kommen von der API Schnittstelle NPGEO.  Auf der Webseite des NPGEO https://npgeo-corona-npgeo-de.hub.arcgis.com/ können wir auf die zentralen Daten der Pandemie zugreifen. Ein Dashboard zeigt uns die aktuellen Zahlen für Deutschland. Und da setze ich an, denn mein Thema für diesen Blogeintrag ist der Abruf der Daten und wie man diese aufbereiten kann.

 

Benötigt:

Visual Studio 2019

 

Anforderung

Über die NPGEO API Explorer soll eine Abfrage zusammengestellt werden. Die heruntergeladenen Daten sollen vom JSON Format eingelesen und in ein Datenobjekt gefüllt werden. Für die Darstellung soll eine Liste mit den Namen der Städten und die 7-Tage-Inzidenz wiedergegeben werden.

 

API Explorer vom RKI

Aktuell muss man etwas herunter scrollen, bis man an die Links mit den freizugänglichen Daten kommt. Uns interessiert der Link zum Robert Koch-Institut.

 

 

Auf der nächsten Seite wählen wir die Datenquelle 'RKI Corona Landkreise' aus und wechseln dort von der Übersicht auf den Tab API-Explorer. Im Rahmen für die Abfrage nehmen wir alle Haken raus, außer für 'cases7_per100k' (7 Tage Inzidenz) und 'GEN' (Name des Landkreises).

 


In Ausgabeoption kann die Rückgabegeometrie abgestellt werden. Anschließend kopieren wir die fertige Abfrage-URL, die  rechts auf der Seite angezeigt wird.



Datenformat betrachten (API-Explorer)

Unten, mit dem Button 'Testen', können die Daten im JSON-Format betrachten werden. Ich weiß nicht, ob ich an dieser Stelle etwas falsch mache, aber bei mir kamen mit den Einstellungen nicht die Daten, die konfiguriert wurden, an. Daher setzte ich auf einen alternativen Weg.



 

Datenformat betrachten (Abfrage-URL)

Die Abfrage-URL können wir über den Browser in einem neuen Tab abrufen und die Daten in einem unformatierten JSON-Format bekommen. Das Ergebnis anschließend speichern mit dem Kontext Menü 'Speichern unter…'. Die Datei sollte beim Benennen mit '.json' enden, damit diese Datei beim Öffnen mit Visual Studio Code oder Notepad++ richtig erkannt wird (ggf. plugin oder Extension installieren, damit eine Formatierung für JSON funktioniert, kann sein, dass die Formatierung auch manuell angetriggert werden muss).

 

WPF Anwendung

Inzwischen kann auch mit .NET Core eine WPF Anwendung geschrieben werden. Für den folgenden Aufbau macht dies jedoch keinen Unterschied, da nur grundlegende XAML Elemente verwendet werden, könnte auch eine UWP Anwendung in Frage kommen.

 

Datenobjekt

Nach dem Erstellen benötigen wir folgenden Datenobjekte.

 

 public class RkiDataResult  
 {  
   public Landkreis[] features { get; set; }  
   public class Landkreis  
   {  
     public Attribute attributes { get; set; }  
     public class Attribute  
     {  
       public string GEN { get; set; }  
       public double cases7_per_100k { get; set; }  
     }  
   }  
 } 

 

Abruf mit WebClient

Hier muss lediglich die Abfrage-URL für den Download eingetragen werden. Die Daten selbst werden dann mit dem 'JsonConvert' Klasse in das zuvor angelegte Datenobjekt umgewandelt. (Die Bibliotek Newtonsoft für 'JsonConvert' wird aus den Nuget Paketen in das Projekt hinzugefügt).


 public class RkiWebClientComponent  
 {  
   public static RkiDataResult LoadAktualData()  
   {  
     var client = new WebClient();  
     client.Headers[HttpRequestHeader.ContentType] = "application/json";  
     try  
     {  
       var urlAddress = "https://services7.arcgis.com/mOBPykOjAyBO2ZKk/arcgis/rest/services/RKI_Landkreisdaten/FeatureServer/0/query?where=1%3D1&outFields=GEN,cases7_per_100k&returnGeometry=false&outSR=4326&f=json";  
       var result = client.DownloadString(urlAddress);  
       return JsonConvert.DeserializeObject<RkiDataResult>(result);  
     }  
     catch (Exception e)  
     {  
       throw e;  
     }  
   }  
 } 

 

Auflisten und Item-Template

Das Grid in MainWindow wird ersetzt durch eine ListBox, welche einen Namen zugewiesen bekommt. Das Template zu einem Item ermöglicht die Informationen zu platzieren. Die Darstellung des 7-Tage-Inzidenz Wert reicht aus (mit einer Nachkommastelle) daher wird im Binding noch ein StringFormat N1 gesetzt. Für das deutsche Zahlenformat, ist das Setzen der Language auf 'DE' erforderlich.


 <Window x:Class="ExampleSimpleReadRkiData.MainWindow"  
     xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"  
     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"  
     xmlns:d="http://schemas.microsoft.com/expression/blend/2008"  
     xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"  
     mc:Ignorable="d"  
     Title="Aktuelle RKI Daten"   
     Height="250" Width="400"   
     Language="DE">  
   <ListBox x:Name="LandkreiseListe"   
        HorizontalContentAlignment="Stretch">  
     <ListBox.ItemTemplate>  
       <DataTemplate DataType="RkiDataResult.Landkreis.Attributes">  
         <Grid>  
           <Grid.ColumnDefinitions>  
             <ColumnDefinition Width="*" />  
             <ColumnDefinition Width="*" />  
           </Grid.ColumnDefinitions>  
           <TextBlock Grid.Column="0"   
                 Text="{Binding GEN}"  
                 Margin="0 0 10 0"/>  
           <TextBlock Grid.Column="1"   
                 Text="{Binding cases7_per_100k, StringFormat=N1}"   
                 HorizontalAlignment="Right" />  
         </Grid>  
       </DataTemplate>  
     </ListBox.ItemTemplate>  
   </ListBox>  
 </Window>  

 

Abruf und in die View laden

In den Behind-Code oder bzw. in die MainWindow.xaml.cs werden die Daten abgerufen und in die ListBox geladen. Diese Aufgabe wird von der Methode 'OnApplyTemplate' übernommen, die hier überschrieben wird. Die Auflistung kann in diesem Zug auch gleich sortiert werden, nach dem 7-Tage-Inzidenz.

 

 public partial class MainWindow : Window  
 {  
   public MainWindow() => this.InitializeComponent();  
   public override void OnApplyTemplate()  
   {  
     var rkiData = RkiWebClientComponent.LoadAktualData();  
     this.LandkreiseListe.ItemsSource = rkiData  
       .features  
       .OrderByDescending(o => o.attributes.cases7_per_100k)  
       .Select(s => s.attributes);  
   }  
 } 

 

Fertig

Der interessante Teil ist, woher man die Daten bekommt und wie man das programmatisch abrufen kann.

Nun, das Ganze lässt sich weiterausbauen und das habe ich auch getan. Auf meinem Github Repository findet ihr dieses Beispiel und eine weitere ausgebaute Version. Diese kann die Daten in der Liste sortieren, nach zwei verschiedenen Werten. Die Daten zu einem District werden zusätzlich in einem chronologischen Diagramm angezeigt. Zu dem speichert die Anwendung die tagesaktuellen Daten, womit eine Historie mit dem Diagramm abgebildet werden kann.

 

Beispiel wie immer

Das Ergebnis kann wieder von meinem Github Repository heruntergeladen werden. 

Github - ExampleReadAndShowRkiData




Altes Beispiel was ich mal angefangen hatte

Ein alteres Beispiel, dass die Daten von einemn zwischengespeicherten Datenhalter kommt. Siehe dazu mehr auf rki.marlon-lueckert.de . Im gegensatz zu dem Beispiel, werden die Daten Lokal gespeichert und ermöglich eine eigene Historie abzubilden in Form eines Diagrames.

Github - ExampleReadAndShowRki (Old Example)


Aufwendinge UI mit den selben Funktionen

Der Folgende Branch ist eine Übung bezüglich die Anwendung mehr Ansehnlicher zu gestalten. Hat aber nicht mehr Funktionen als mein altes Beispiel (aber auch nicht alle). Bezieht jedoch die Daten direkt vom RKI und nicht durch einen zwischen Datenhalter. Also wie aus dem Beispiel bereits beschrieben. 

Github - OverviewRkiData


 

Eine weitere API Quellen zum Thema Corona

Der folgende Link führt zu einer weiteren Quelle. Für die, die sich lieber die internationalen Daten ansehen wollen. Mit der Doku über POSTMAN, werden dort auch schon fertige Code Snippeds angezeigt.

 

Corona COVID 19 API (POSTMAN)


Dienstag, 27. Oktober 2020

Welchen Motortreiber verwenden


Auf dem Markt gibt's viele Motortreiber für bestimmte Projekte, aber im Detail unterscheiden sich diese bezüglich ihrer Eignung.

Mein Projekt wird ein neuer Motortreiber für die Lego Powerfunction Motoren. Zuletzt hatte ich einen Motortreiber für zwei Motoren gebaut und programmiert, jetzt soll ein Motortreiber für acht Motoren entstehen.

 

Motortreiber MX1508

Den verwendeten Motortreiber MX1508 findet man fälschlicherweise auch unter L298N. Der bereits gebauten Powerfunction Treiber funktioniert prima und ist im Grunde meine erste Wahl. Allerdings möchte ich ein wenig mehr Spannung haben oder zumindest mehr bei der Versorgungsspannung. Maximal 10V sind in Ordnung, womit aber 3S Akkus mit einer Spannung von 11,1V raus sind. Leider ist für diesen Motortreiber kein Datenblatt zu finden.


 

Motortreiber TB6612FNG

Dieser ist ebenfalls so günstig wie der MX1508, kann jedoch mit einer höheren Spannung betrieben werden. Statt auf 2S Lipos, wäre hier auch 3S möglich. Vielleicht 4S, aber das liegt mir dann doch etwas zu sehr drüber.

Klar sollte sein, dass die Powerfunction für eine Betriebsspannung von 9V ausgelegt ist. Wenn man sich im Internet umschaut, findet man einige Bastler, die mehr als das Dreifache an Spannung angelegt haben, bevor solch ein Lego-Motor das Zeitliche gesegnet hat.


 

Motortreiber L298N

Dieser kostet deutlich mehr und findet in meinem Projekt keinen Platz. Ich habe ihn hier mit aufgeführt, weil die Bezeichnung L298N, oft mit dem Motortreiber MX1508 zusammengebracht werden. Das ist schlicht weg falsch und spätestens mit der Betrachtung des Datenblattes L298N auch ersichtlich. Denn alleine die Bauform zwischen beider Motortreibern unterscheiden sich und die Leistungsdaten gehen hier weit auseinander.

 

 


 

Ansteuerung der Module

Prinzipiell werden alle Motortreiber idealerweise mit einem PWM Signal angesteuert. Alle funktionieren mit der ansteuerbare Frequenz von 50Hz, welche auch von vielen gängigen RC Empfänger ausgegeben wird. Höhere Frequenzen sind sinnvoll bei Schrittmotoren mit Einsatzgebieten wie balancierende Roboter oder die Drehzahl der Rotoren bei Quadrocopter, welche eine genaue, bestimmte Drehzahlen erreichen müssen.



Die Lego Powerfunction Motoren sind keine Schrittmotoren und die Lego Servos sind enttäuschend ungenau, so das höhere Signalfrequenzen nicht wirklich eine Verwendung finden. Also kein Kriterium.

 

Steuerbare Spannung und Strom

Die größte Last schaffen die Lego Powerfunction XL Motoren und können bis zu 600mA Strom ziehen. Auch hier erfüllen alle Motortreiber die Anforderungen für das Projekt und alle bieten noch Spielraum, einen zweiten Motor parallel zu betreiben.

 

Bauform und Größe

Der L298N habe ich selbst nicht, aber wenn man im Internet schon das Bild sieht, sind nicht nur die Kondensatoren auffällig, sondern auch dessen Anschlüsse und der große Kühlkörper benötigen deutlich mehr Platz. Damit fällt dieser dann auch komplett raus.

Der Kompakteste ist der TB6612FNG. Am MX1508 sind wiederum Kondensatoren, womit die Bauhöhe um einige Millimeter höher ausfällt. An der Stelle ist sicher zu stellen, ob nun am TB6612FNG noch zusätzliche Kondensatoren benötigt werden. Einmal googlen und schon findet man von SparkFun ein Beispiel, dass zeigt, das keine weiteren Kondensatoren verlötet werden müssen.

Kompensatoren zum Entstören sind nicht nötig, da diese bereits in den Lego Motoren immer mit verlötet werden.

 

Zusammenfassung der Kriterien

Die Bauform war der Grunde für die Entscheidung des zu empfehlenden Motortreiber. Rückblickend waren doch einige interessante Unterschiede zu entdecken, die für den einen oder anderen Bastler interessant sein dürften.

 


Was den Preis angeht, ist der Kauf einer Zweier- oder Fünfer-Packung deutlich günstiger. Die 6 € für einen TB6612FNG ist doch schon eine große Preisunterschied zu den anderen Motortreibern.

 

Fazit

Welcher Motortreiber soll es sein? Die Frage ist natürlich vom Projekt abhängig. Eigentlich hätte für meinen Bedarf auch schon der Motortreiber MX1508 ausgereicht. Denn hier können die Lego Powerfunction Motoren nahezu in ihrer Nennleistung von 9V betrieben werden. Und wenn doch ein 3S 11,1V Lipo Akku eingesetzt werden sollte, dann könnte an der Stelle einen Step-Down davor geschaltet werden. Aber inoffiziell können die Motoren mit mehr Spannung um, womit noch etwas mehr Drehzahl und Drehmoment zu erwarten sind.

Bei meiner abschließenden Betrachtung aller Kriterien fiel ich immer wieder auf dieselben Themen zurück. Datenblatt, Größe, Versorgungsspannung und Betriebsstrom.

Der Preis war eher irrelevant, auch wenn diese sich stark unterschieden. Dennoch habe ich das in der bereits aufgestellten Tabelle aufgenommen.

Auch wenn klar sein sollte, wie ein Motortreiber angesteuert wird, ist ein Datenblatt hilfreich. Das liegt mit unter daran, dass die Bezeichnung in Web-Shops nicht immer stimmen und nur über den Vergleich im Datenblatt weiterhilft, ob man nun das richtige Produkt hat. Und das brachte mich zu der Information der Baugröße, Versorgungsspannung und Betriebsstrom.

 

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)

Mittwoch, 24. Juni 2020

RC Fahrtenregler für Lego Kettenfahrzeug



 

Auf dem Markt gibt es viele verschiedene Module an Motortreibern. Diesmal verwende ich einen recht einfachen, der zur Ansteuerung bis zu vier PWM Signale verwendet. Der Motorschield für den Arduino hingegen benötigt noch eine Steuerung eines Shiftregister 74HC595. Ich weiß, das geht auch ohne, aber ich beziehe mich diesmal auf fertige Module, die kompakt aufgebaut sind.

Aber nur ansteuern wäre diesmal zu langweilig und daher habe ich hier als Ziel, den Lego Technic Stunt-Racer damit zu betreiben. Wer das Modell kennt, weiß, dass dieser mit einer Infrarot Fernbedienung ausgeliefert wird und das eher nur in der Wohnung funktioniert.

 

Benötigt wird:

Modul MX1508 (DC Motor Treiber)

Arduino Mini Pro (5V Ausführung)

2x Servo Kabel

2x Lego Power Function Verlängerungskabel

Lego Technic Fahrzeug mit Power Function

Zwei Kanal Fernsteuerung und ein Empfänger mit PWM Signal Ausgängen.

 

Betrieb mit Funk-Fernsteuerung

Der Microcontroller soll zwei PWM Signale verarbeiten, so das damit zwei Motoren gesteuert werden können. Als Fernsteuerung soll links der Stick für die Geschwindigkeit sein und rechts der Stick für das Lenken. Also wie bei jedem anderen RC Auto.

 

Pin Belegung Power Function Kabel

Für die Umsetzung reicht es zu wissen, welche Leitung von den vier was ist. Bei der Suche war immer die Beschriftung 9V, 0V, C1 und C2 gegeben, weshalb ich das ebenfalls übernehme.

 

 

Für den nächsten Schritt müssen die beiden Power Function Kabel so abgeschnitten werden, dass die Verbindung für das eigene Projekt passen.

 

Motor Treiber MX1508

Den verwendeten Motor Treiber MX1508 findet man fälschlicherweise auch unter L298N. Der Motor Treiber L298N funktioniert ebenfalls für dieses Vorhaben, ist allerdings etwas übertrieben und ist auch eher was für größere Projekte geeignet.

 

Daten:

Betriebsspannung 2V bis 10V

Signal Spannung für IN1 bis IN4 1,8V bis 5V

Ausgangspannung für Motor-A und Motor-B 2V bis 10V

Betriebsstrom 1,5A

Maximaler Kurzstrom 2,5A

 

 

 

Aufbau der Schaltung

Der folgende Schaltplan zeigt, wie die Power Function Kabeln mit dem Motortreiber verbunden werden. Die Schaltung funktioniert auch mit einem Lipo Akku. Daher kommt neben dem Power Function Stecker noch ein JST Anschluss. Der Arduino Mini Pro wird über die Spannungsversorgung mitbetrieben und über den Pin RAW angeschlossen.

 

 

 

Der fertige Motortreiber

Anders als auf dem Bild zuvor habe ich noch einen weiteren Anschluss für die Spannungsversorgung angebracht. Das Ermöglicht auch zwei Zellen Lipo Akkus zu verwendend, anstatt der Lego Powerfunction Battery Box. Links auf dem Bild sind vier Leitungen mit Steckern, die für den Anschluss eines Empfängers da sind.

 



Programmcode einstellen

Diesmal beschreibe ich nicht den Aufbau des Programmcode, sondern gehe auf einen fertigen Motortreiber von mir ein. Für das Zielprojekt soll die Eingabe der Fernsteuerung links für die Geschwindigkeitsregelung sein und rechts das Lenken. Also ganz klassisch. Natürlich ginge auch eine Steuerung, wie sie bereits von Lego Power Function mit der Infrarot Fernbediendung vorgegeben wird.

 

Einstellung für den Lego Technic Stunt-Racer

 // Set this value true, for show all Value on Serial Monitor.  
 bool mSerialMonitor = false;        
 // 0 = Car, Input 1 for speed and input 2 for steer  
 //  - Alternate: Tracked vehicle, Input 1 for speed motor A and   
 //         Input 2 for speed motor B  
 // 1 = Tracked vehicle, Input 1 for speed and input 2 for steer  
 int mVehicleMode = 1;           
 // invert steering direction, only tracked vehicle   
 boolean mVehicleModeSteerInvert = false;  
 // Tolerance range in which no reaction should take   
 // place when sticks are in the middle position.  
 int mDeathbandPlusMinus = 10;       
 // change direction signal output for motor A  
 boolean mReverseA = true;  
 // middel state of the input signal. Example: stick on neutral position  
 int mInputMiddleA = 1500;  
 // minimum value of input signal. Example: stick is move complete down  
 int mInputMinA = 1000;  
 // minimum value of input signal. Example: stick is move complete up  
 int mInputMaxA = 2000;  
 // change direaction sinal output for motor B  
 boolean mReverseB = false;  
 // middel state of the input signal. Example: stick on neutral position  
 int mInputMiddleB = 1500;  
 // minimum value of input signal. Example: stick is move complete down  
 int mInputMinB = 1000;  
 // minimum value of input signal. Example: stick is move complete up  
 int mInputMaxB = 2000; 

 

 

Lego Technic Stunt-Racer Umbau

Damit der Empfänger und der Motor Treiber nicht einfach so rum hängen, sollte der Aufbau etwas geändert werden. Der Lego Technic Infrarot Empfänger wird entfernt. Wie nun der Umbau aussieht, ist zweitrangig, Hauptsache die Elektronik ist gut verstaut. Der folgende Umbau ist ein Beispiel.

 


   
 
   
   
   
   

 

Wort zum Schluss

Die Motoren lassen sich noch mit einer höheren Spannung betreiben. Allerdings steigt dann auch die Belastung der Lego Teile in Form von Verschleiß, wegen der fehlenden Kugellager.

Dann noch der hier verwendete Motor Treiber MX1508: Wegen einer falsch ausgewiesenen Information zu L298N, dachte ich zunächst, dass eine höhere Spannung möglich sei. Nach dem ich später für den Blog Eintrag mehr Informationen über den verwendete Komponente suchte, stieß ich auf die richtigen Werte. Zugegeben, ich hatte bereits mit der Fehlinformation den Aufbau mit einem 3S Akku betrieben. Allerdings nur kurz. Für den Betrieb mit einer hören Spannung würde dann der Motor Treiber L293D gehen, aber dann könnten Probleme mit dem maximalen Strom geben, wenn XL-Motoren verwendet werden.

 

Arduino Code - Motortreiber für MX1508

Github - Powerfunction-MotorDriver

 


Mit Claude Blender verwenden

Das geht besser als ich dachte und ich habe nur Low Polygone Landschaften probiert. Mein Prompt zum Foto: Lese das Bild ein und erstelle dar...