Sonntag, 28. März 2021

Da will man wieder... (Meadow)


Seit Monaten lag mein Meadow Microcontroller unbenutzt im Schrank, wie auch die eingekauften Sensoren, die teilweise noch verpackt waren. Mein Ziel war eigentlich am Samstag ein neues Code-Beispiel zusammen zu schreiben für die Verwendung eines CO2 Sensor im .NET Umfeld.

Also begann ich mit dem Lesen von Dokumentationen und bereits bestehenden Code aus der C++ Welt und schrieb entsprechend dann die .NET Lösung. Dann sollte der Programmcode auf den Meadow geschrieben werden … aber es kam anders. Ein Blogeintrag über das Suchen und Finden von Fehlern, bevor ein Blogeintrag entsteht..


Fehler beim Verwenden von Dingen

Nicht aussagekräftige Fehler können einen aufhalten und kosten Zeit. Letztes Mal war es das Bereitstellen mit F5, welches bei allen Anwendungen zum Kompilieren und Starten genutzt wurde, welches aber bei dem Meadow nicht so funktioniert. Hier muss mit der rechten Maus-Taste auf das Projekt und anschließend auf Deploy geklickt werden. Einfach, aber so etwas kann einen echt ausbremsen.
 

Dieses Mal trat der Fehler beim Verwenden der Komponenten aus dem Meadow Framework auf, welches folgende Meldung im Debug Output zeigte:

ioct1 Esp32Command failed OperationNotPermitted

Der Satz sagte mir nicht, dass meine MeadowOS Version zu alt ist. Mein letztes Projekt vom August 2020 verblieb bis zuletzt unverändert. Aber auch diese funktionierte nicht mehr und stieg mit der Fehlermeldung aus, dass die Datei Mono.Security.dll nicht gefunden wurde.

Welche Abhängigkeiten habe ich hier an der Stelle, wenn ich nicht das NuGet Packet Meadow.Foundation v0.17.0 und sowie auch das .NET Framework 4.7.2  geändert habe?


Das alleine reichte nicht

Wenn ich Deploy geklickt hatte, dann hing Visual Studio. Das ließ sich beheben, indem ich die USB- Verbindung zum Meadow trennte. Idealerweise muss man auf dem Meadow nur den Reset Button drücken, damit das Deployment weiter fortschreitet.

 

Niemals ein funktionierendes System ändern?

Oder besser bekannt: never change a running system. Wieso also kann es nicht weiterhin funktionieren, wenn man nichts verändert? OK, ich habe Visual Studio 2019 immer aktualisiert, aber ich aktualisiere nicht immer die einzelnen Projekte. Außer das verwendete Framework ist so alt, das man dessen Frameworks nachinstallieren muss.

Ich habe eine alte Windows Form Anwendung, die selbst nach über 12 Jahre immer noch geht. Ok, ich muss hier .NET Framework 2.0 nachinstallieren, welches automatisch nachgefragt wird, und nur noch zustimmen.

 

Aktualisierung benötigt

Weiter geht’s: Ich komme nicht dran vorbei, die entsprechenden Aktualisierungen am Projekt vorzunehmen und auch den Meadow mit einer aktuelle OS Version zu flashen. Auf der Webseite 'Wilderness Labs' kann man die aktuellen Binaries herunterladen.
Wie sich später herausstellte, sind es zwei Zip Datei. Beim nochmaligen Durchlesen der Beschreibung "Deploy Meadow OS" stellte ich fest, dass die Beschreibung selbst aktualisiert wurde.
Finde ich gut, aber der Textaufbau auf der Seite ist nicht linear und erschwert das Lesen, obwohl die Beschreibung relativ sauber beschrieben ist. Mal ist der auszuführende Befehl rechts und mal direkt im Text eingerückt. Vielleicht eine Kleinigkeit und vielleicht ein Problem meiner Wahrnehmung, weil ich scheuklappenartig den Text lese und am Rand eh nur Werbung vermute (Anmerkung an der Stelle, auf der Webseite sind keine Werbe Ads vorhanden).

 

Schritt für Schritt

Gehen wir also nochmal Punkt für Punkt die Beschreibung durch:

Haste .NET 5.0 SDK?
Nein. Wird also installiert.

Wie sieht es mit Meadwo.CLI aus?
Auch nicht. Wird installiert.

Update USB Treiber für Bootloader?
Noch aktuell.

Aktuelle Meadow OS und Network Binaries?
Hatte ich heruntergeladen.
Später sah ich rechts im blauen Feld den Befehl, mit dem sich die aktuellen Version einfach in den vorgesehenden Ordern herunteladen lässt und dessen ZIP Inhalt auch automatisch entpackt. (COMMAND: meadow --Download)

Meadow in den Bootloader Modus umschalten?
Geht. Boot Button gedrückt halten und dann anschließen.

Meadow.OS und Coprocessor Firmware flashen mit dem Befehl: meadow --FlashOS?
Geht nicht. Endet mit der Unbehandelten Ausnahmefehler: System.ComponentMode.Win32Exception

Grrrr… Why?!?

 

Die Community fragen

Davon verspreche ich mir in der Regel nicht viel. Anscheinend ist das Thema 'Lösungen' in einem Forum untergegangen, seit dem Stackoverlfow exisitiert. Damit will ich nicht ausschließen, dass sich dort keine Lösungen finden. Man muss halt nur ein paar mehr Seiten lesen und nicht hilfreiche Kommentare ertragen.

Meine letzte Frage in dem Forum wurde bis heute nur von mir selbst beantwortet. Vielleicht peinlich, dass ich nicht mehr wusste, dass die Nutzungserfahrung für das Bereitstellen beim Meadow nicht mehr gelten.

Dass eine Frage von mir nicht von einem anderen User beantwortet wurde, ist nicht das erste Mal. Gleiches Forum, anderer Microcontroller. Fairerweise muss man sagen, dass dort nicht viele Nutzer aktiv sind und auch somit nicht viel geantwortet wird.

 

Try and Error

Nacheinander habe ich die Meadow OS Versionen heruntergeladen und dessen Inhalt betrachtet. In älteren Version fand ich auch dann die damalige verwendete CLI. Leider fand ich den verwendeten Befehl nicht wieder und wusste auch nicht direkt, nach was ich im Netz suchen musste.

Einen Tag später suchte ich weiter nach einer Lösung und stellte fest, dass ich wohl ein Problem mit dem Dfu-util hatte beziehungsweise dieser schlicht weg fehlte. Der folgende Befehl installiert das Tool in den Windows/System32 Order. Ggf. muss CMD oder PowerShell als Administrator ausgeführt werden.

meadow --InstallDfuUtil

Nach dem Installieren konnte nun endlich der Flashvorgang erfolgreich durchgeführt werden.
Nun erklärte sich mir ENDLICH die zuvor aufgetretene Exception, welches als Information nicht aus der Meadow.Cli ausgegeben wurde.
Ok, vielleicht hätte der Fehlertext "…\DfuUpload.cs:line 141 …" mich darauf aufmerksam machen sollen.


Endlich den Meadow benutzen… oder auch nicht

Nachdem ab- und anschließen des Meadow Boards und öffnen der Solution, führte ich den 'gewohnten' Deploy Vorgang aus. Die Spannung stieg!!
Ich sah zu, wie im Output Fenster die Daten hochgeladen wurden. Beim letzten Eintrag wechselten hier der Tab zu "Error List". Hier wurde nichts angezeigt, dafür stand im letzten Eintrag vom "Output" einer neuen Exception… Grrr

Erfolgreich, aber die MeadowClie scheint nicht zu funktionieren

 

Zurück zur Beschreibung

Wiederholt rief ich die Beschreibung auf Wilderness Lab auf und versuchte zu deuten, ob der Schritt drei danach richtig eingerückt war oder noch zu der Beschreibung mit Linux gehörte. Anscheinend ist Schritt drei wirklich Schritt drei und somit war der Aktualisierungsvorgang noch nicht durch. Folgender Befehl:

meadow --MonoDisable -s [PORT]

Wenn der Befehl hängt, dann Reset-Button auf dem Meadow Board und nochmal.


Dann…

meadow --MonoUpdateRt

… und wenn es wieder hängt, Reset und nochmal.


Und zuletzt noch einer…

meadow --FlashEsp

… der sollte anschließend normal durchlaufen.

 

USB Stecker ab- und wieder anschließen

Kommt mir vor wie bei Karate Kid mit auftragen und polieren. War der Vorgang erfolgreich, sollte das Demo Programm laufen. Das ist dann daran zusehen, wenn die LED ständig die Farbe wechselt.


Endlich den Meadow benutzen… yeah es geht

Langer Weg zum Ziel und ich kann mein eigentliches Vorhaben wieder aufnehmen. Das kommt dann mit dem nächsten Blogeintrag. Wenn nicht schon wieder ein Update ansteht...


Rückblickend

Letzten Endes fehlten mir die Erfahrungen mit welche Probleme ich rechen muss.
Ärgerlich, wenn so viel Zeit mit dem Suchen nach Fehlern drauf geht. Keine Ergebnisse zu finden und dabei auf der falschen Fährte unterwegs zu sein. Deshalb schrieb ich diesen Eintrag, allerdings brachte mich meine Freundin darauf, diesen Vorgang zu einem eigenen Thema zu machen.
Und eigentlich sind die Wege zu den Lösungen zu unserer Problemen, die wir als Bastler im Internet suchen, auch mal ein Beitrag wert.

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)

Robokopf Designen

Ich probiere mich noch, mein richtige Design Vorstellung eines Roboterkopfes zu modulieren in Blender. Das Grundgerüst ist schon grob da, mu...