Windows 10 IoT Digital I/O Performance, Teil 3

In Teil 1 und 2 dieser Flurfunkserie wurde die Performance von Windows 10 IoT bei der Verarbeitung von Digital I/O Signalen analysiert. Als Fazit wurde festgestellt, dass Windows 10 IoT nicht einmal annähernd die Möglichkeiten der Raspberry Pi Plattform ausreizt. Hier sollen die Lightning Providers für Windows 10 IoT eine erhebliche Verbesserung bringen.

Setup der Lightning Providers

Voraussetzung für die Verwendung der Lightning Providers ist eine Windows 10 IoT Version von mindestens 10.10.0.10586.63 (Windows 10 1511). Die Einrichtung erfolgt in drei Schritten:

1. Zunächst wird auf dem Raspberry Pi 2 der Default Controller Driver vom standardmäßig eingestelltem Inbox Driver auf den Direct Memory Mapped Driver umgestellt. Dies erfolgt im Management Web von Windows 10 IoT:

Inbox Drivers

Default Inbox Driver

 

Memory Mapped Drivers

Direct Memory Mapped Driver

2. Zusätzlich wird im Visual Studio Projekt die Lightning Library als Nuget Package mittels Install-Package Microsoft.IoT.Lightning eingebunden.

3. Im Package.appxmanifest sind die folgenden Zeilen unter dem <Capabilities> Node hinzuzufügen:

<iot:Capability Name="lowLevelDevices" />
<DeviceCapability Name="109b86ad-f53d-4b76-aa5f-821e2ddf2141"/>

 

Damit sind alle Voraussetzungen zum Einsatz der Lightning Library geschaffen. Die Entwickler der Library haben das Ziel, beim Zugriff auf die Hardware komplett Api kompatibel zum Standardverfahren zu sein. Alle Objekte und Methoden sollen unterschiedslos zur Verfügung stehen. Die Verwendung der Lightning Library wird lediglich initial einmal beim Start des Programms über den folgenden C# Code festgelegt:

   1: if (Microsoft.IoT.Lightning.Providers.LightningProvider.IsLightningEnabled)

   2: {

   3:     LowLevelDevicesController.DefaultProvider = LightningProvider.GetAggregateProvider();

   4: }

Der komplette restliche C# Code kann unverändert bleiben.

Ergebnisse

Zur Messung der Digital I/O Performance wurde der in den Teilen 1 und 2 der Artikelserie benutzte Code benutzt. Lediglich die oben dargestellte Initialisierung der Lightning Library wurde ergänzt. In der folgenden Tabelle sind die gemessenen Werte für das reine Lesen und Schreiben Digitalein- und ausgängen (Verfahren 1) für die Windows 10 Versionen 1511 und 1608 dargestellt. Zum Vergleich wurde noch der beste gemessene Wert bei Verwendung des Inbox Drivers und der Standard Library aus Teil 1 mit aufgeführt:

Windows 10 IoT Version Frequenz Lesen Dauer Lesen Frequenz Schreiben Dauer Schreiben
1511 1,7MHz 589ns 1,9MHz 531ns
1608 1,7MHz 576ns 1,9MHz 525ns
1608 Inbox 0,2MHz 4100ns 0,2MHz 4300ns

Der Performancesprung ist beeindruckend. Die Ausführungsgeschwindigkeit ist um fast eine Größenordnung besser als bei Benutzung des Standardverfahrens mit den Inbox Treibern.Die Werte sind zwar immer noch weit vom theoretisch Machbaren entfernt. Dennoch zeigt die Lightning Library, welches Potential in Windows 10 IoT und der Programmierung mit Universal Apps bei der Verarbeitung von digitalen Hardwaresignalen steckt.

Leider ergibt sich bei der Analyse der Reaktionsgeschwindigkeit auf eine Signaländerung an einem Digitaleingang (Verfahren 2) ein ganz anderes Bild:

Die Implementierung des ValueChanged Events für Digitaleingänge funktioniert leider weder mit 1511 noch 1608. Erst mit Insiderpreviews ab 1608 wurde das Event implementiert. Dabei muss die Performance noch verbessert werden: Sind unter Verwendung des Default Inbox Drivers und der Standard Library 300µs als Reaktionszeit erreichbar, so sind dies unter Lightning bestenfalls 670µs. Dies bedeutet eine Verschlechterung um den Faktor 2. Damit wird das in Teil 2 geschilderte Anwendungsszenario eines Rotary Encoders praktisch nicht mehr umsetzbar. Hier hilft dann nur noch zyklisches Pollen der digitalen Eingänge und das Verarbeiten von Signaländerungen mit eigenem Code.

Fazit

Die Ergebnisse der Lightning Library sind vielversprechend. Es drängt sich allerdings der Eindruck auf, dass das Projekt noch Zeit benötigt, um die notwendige Stabilität und Reife zu gewinnen. So ist die angekündigte Kompatibilität zum Inbox Driver noch nicht erreicht: Es fehlen noch wichtige Properties wie z.B. der DebounceTimeout für GpioPin Objekte, mit denen z.B. das Prellen von mechanischen Schaltern herausgefiltert werden kann.

Auch abseits von GpioPin  gibt es noch Probleme: So war lange Zeit die Verwendung von I²C Bus Hardware nicht möglich, weil Timings nicht gepasst haben. Zur Zeit kann nur einer der zwei möglichen SPI Busse des Raspberry Pi benutzt werden. Teilweise kämpft der Entwickler mit nicht kompatiblen oder implementierten Methoden und Properties. Code, der mit dem Inbox Driver läuft, wird problemlos mit Lightning übersetzt, funktioniert aber dann nicht oder anders als erwartet.

Dennoch zeigt das Projekt, welche Performancereserven in Windows 10 IoT vorhanden sind. Es bleibt abzuwarten, bis das Projekt stabile Versionen liefert, die die erwartete Kompatibilität bei besserer Performance erfüllen. Dann steht auch einem produktiven Einsatz nichts im Wege.

Windows 10 IoT Digital I/O Performance, Teil 2

Im ersten Teil der Serie wurden grundlegende Aspekte der Digital I/O Performance von Windows 10 IoT diskutiert und die rohe Verarbeitungsgeschwindigkeit gemessen. In diesem Teil wird die Fragestellung untersucht, wie schnell Windows 10 IoT auf einem Raspberry Pi 2 oder 3 auf Änderungen eines externen Hardwaresignals reagieren kann.

Dies ist beim Einsatz vieler Hardwarebausteine von entscheidender Bedeutung, da hier häufig vom Datenblatt eng definierte Timings bei der Signalverarbeitung einzuhalten sind.

Implementierung Rotary Encoder

In einem konkreten Projekt des Autors wird ein digitaler Drehgeber oder Rotary Encoder zur Einstellung von Parametern und Navigation im Menü verwendet. Rotary Encoder ersetzen klassische Potentiometer und erzeugen bei Drehen des Gebers digitale Signale, die sich nach Drehrichtung (in oder gegen den Uhrzeigersinn) unterscheiden. Je nach Auflösungsvermögen des Encoders kann eine komplette Drehung um 360° 10000 und mehr Impulse erzeugen. Der aktuell im Projekt verwendete Encoder stellt für die Übermittlung von Drehimpulsen zwei digitale Ausgangssignale Clock und Data zur Verfügung, die vom Raspberry Pi 2 über Digitaleingänge verarbeitet werden müssen. Eine steigende Flanke von Clock leitet einen Drehimpuls ein. Eine fallende Flanke von Clock beendet den Drehimpuls. Die Drehrichtung wird dann vom Level des Data Signals bei fallender Flanke von Clock definiert. Ist Data low, wurde der Encoder im Uhrzeigersinn gedreht. Ist Data high, erfolgte eine Drehung gegen den Uhrzeigersinn. Das folgende Diagramm verdeutlicht den Signalverlauf, wobei A dem Signal Data und B dem Signal Clock entspricht:

Encoder Timing

Der Signalverlauf wird in 4 Phasen unterteilt:

1. Beginn der Drehung, Data wird je nach Drehrichtung eingestellt.
2. Ansteigende Flanke von Clock, Signal an den Rechner, dass eine Drehbewegung stattfindet.
3. Data wird invertiert.
4. Fallende Flanke von Clock, Bewegung ist beendet, Data gibt die Drehrichtung an.

Für die Signalverarbeitung ist entscheidend, dass die steigende Flanke von Clock mit Phase 2 einen Eventhandler im Programmcode anstößt. Der Eventhandler wartet bis zur fallenden Flanke von Clock in Phase 4, liest währenddessen fortwährend den Zustand von Data und signalisiert bei Ende eine Drehbewegung in die ermittelte Drehrichtung. Wie im ersten Teil dargestellt, ermöglicht die Klasse GpioPin über das Event ValueChanged auf Signaländerungen an einem Digitaleingang zu reagieren. Der folgende C# Code implementiert genau dieses Verfahren (Achtung: Das Clock Signal liegt invertiert an):

   1: /// <summary>

   2: /// Event handler triggered on rotary encoder's clock signal changes. Note: Signals have inverted logic.

   3: /// </summary>

   4: /// <param name="sender"></param>

   5: /// The sending rotary encoder clock pin

   6: /// <param name="args">The pin state changed event args</param>

   7: private void PinClockValueChanged(GpioPin sender, GpioPinValueChangedEventArgs args)

   8: {

   9:     if (Rotated != null) {

  10:         // Only do something if there are listening event consumers

  11:         if (args.Edge == GpioPinEdge.FallingEdge && this.pinClock.Read() == GpioPinValue.Low) {

  12:             // Only do something, if a falling edge of the pin signal is detected

  13:             GpioPinValue data;

  14:

  15:             // Get valid signal on data pin as long as clock signal remains low. Then rotation direction

  16:             // is determined by data pin signal.

  17:             do {

  18:                 data = this.pinData.Read();

  19:             } while (this.pinClock.Read() == GpioPinValue.Low);

  20:

  21:             // Notify event listeners about switch rotation. Rotation is determined by data pin value on rising edge

  22:             // of the clock pin

  23:             Rotated(data == GpioPinValue.High ? Rotation.CounterClockwise : Rotation.Clockwise);

  24:         }

  25:     }

  26: }

Rotated ist dabei ein Event, das von der Klasse zum Management des Rotary Encoders bereitgestellt wird, um Subscriber über eine Drehbewegung zu benachrichtigen.

Es leuchtet ein, dass die Verarbeitung der der Rotary Encoder Impulse nur funktionieren kann, wenn der Eventhandler sicher in Phase 2 und deutlich vor Phase 4 aufgerufen wird. Nur dann besteht die Chance, Drehimpulse mit ihrer Drehrichtung korrekt zu erkennen. Beim vom Autor eingesetzten Encoder liegt die Dauer von Phase 2 bis Phase 4 bei ca. 500µs. Diese Dauer kann je nach Auflösung des eingesetzten Bausteins variieren.

Messung

Unter Windows 10 IoT 1507 war es nicht möglich, den Rotary Encoder fehlerfrei zu betreiben. Daher wurde eine Messung der Antwortzeiten durchgeführt. Dafür wurde ein Digitalausgang (#5) direkt mit einem Digitaleingang (#6) des Raspberry Pi verbunden, mit Schreiben des Ausgangs eine Stoppuhr gestartet und bei Erreichen des Eventhandlers die abgelaufene Zeit ausgelesen. Dies wird über die folgende Methode erreicht:

   1: /// <summary>

   2: /// Does the event timing measurment.

   3: /// </summary>

   4: /// <returns></returns>

   5: public async Task DoEvents()

   6: {

   7:     var gpioController = await GpioController.GetDefaultAsync();

   8:     AutoResetEvent goOn = new AutoResetEvent(false);

   9:     // Open pin #6 as digital input

  10:     using (var gpioReader = gpioController.OpenPin(6, GpioSharingMode.Exclusive)) {

  11:         gpioReader.SetDriveMode(GpioPinDriveMode.Input);

  12:         // Open pin #5 as digital output

  13:         using (var gpioWriter = gpioController.OpenPin(5, GpioSharingMode.Exclusive)) {

  14:             gpioWriter.SetDriveMode(GpioPinDriveMode.Output);

  15:             // Init output

  16:             var outputValue = GpioPinValue.High;

  17:             gpioWriter.Write(outputValue);

  18:

  19:             // Define event handler measuring time to process input signal change

  20:             gpioReader.ValueChanged += (sender, args) => {

  21:                 this.PerformanceCounter.Measure();

  22:                 Task.Delay(500).Wait();

  23:                 goOn.Set();

  24:             };

  25:             this.PerformanceCounter.Reset();

  26:

  27:             // Loop until measurements are stopped

  28:             while (this.isMeasurementActive) {

  29:                 // Negate output, trigger signal change on input #6

  30:                 outputValue = outputValue == GpioPinValue.High ? GpioPinValue.Low : GpioPinValue.High;

  31:                 this.PerformanceCounter.Pulse();

  32:                 gpioWriter.Write(outputValue);

  33:                 goOn.WaitOne();

  34:             }

  35:         }

  36:     }

  37:     this.stopMeasurement.Set();

  38: }

Die Messung ergibt für alle untersuchten Windows 10 IoT Versionen (1507, 1511 und 1608) einen durchschnittlichen Wert von ca. 300µs als Zeit, die von der Änderung des Eingangssignals bis zum Erreichen des Eventhandlers benötigt wird. Damit ist eine sichere Verarbeitung der Encoder Signale eigentlich sichergestellt. Allerdings ist bekanntermaßen die Implementierung des ValueChanged Events unter 1507 nicht stabil, was die Probleme mit dieser Version erklärt.

Bewertung

Anders als beim direkten Lesen oder Schreiben der Digital I/Os hat Microsoft hier im Versionsverlauf keine Verbesserung der Performance erreicht. Auch hier gilt wie in Teil 1 beschrieben, dass dies unter anderen Betriebssystemen deutlich performanter geht. So sind bei Einsatz eines Kernelmoduls unter Linux Antwortzeiten von 15µs erreichbar. Dies ist um einen Faktor 20 besser als Windows 10 IoT und lässt Letzteres als Alternative bei der Umsetzung von Hardwareprojekten ausscheiden.

Im dritten und letzten Teil der Serie wird die Lightning Bibliothek vorgestellt. Sie hat sich zur Aufgabe gemacht, die Performance von Windows 10 IoT bei der Verarbeitung von Hardwaresignalen deutlich zu verbessern.

Windows 10 IoT Digital I/O Performance, Teil 1

Seit Microsoft im vergangenen Sommer sein Windows 10 IoT für das Internet of Things herausgebracht hat, ist eine rege Entwicklergemeinde entstanden, die Hardwareprojekte auf Basis dieses Betriebssystems realisiert. In dieser Artikelserie wird die Performance des Systems bei der Verarbeitung digitaler Hardwaresignale untersucht.

Insbesondere die Unterstützung der äußerst populären Hardwareplattform Raspberry PI 2 und 3 machen die Verwendung von Windows 10 IoT interessant. Als Mitglied der Universal Windows Platform Familie ermöglicht diese Windows 10 Variante für Geräte es Entwicklern, die gewohnte Toolchain mit Visual Studio 2015 zu benutzen und dabei gelernte Skills weiter zu verwenden.

Digital I/O

Die Raspberry Einplatinencomputer stellen über einer Stiftleiste eine Vielzahl an Signalen zur Verfügung, die bei der Kommunikation mit Hardwarekomponenten eingesetzt werden können: 2 serielle SPI Busse, 1 serieller I²C Bus sowie 24 digitale Ein- und Ausgänge (Digital I/O). Letztere können beliebig als Eingang oder Ausgang konfiguriert werden und spielen bei praktisch allen Einsatzszenarien eine entscheidende Rolle. Fast jeder Hardwarebaustein, der an den Raspberry gekoppelt wird, nutzt zur Steuerung digitale Ein- und Ausgänge.

Sollen anspruchsvollere Aufgaben als z.B. das simple Blinken einer LED im Sekundentakt erledigt werden, ist durchaus die Performance von Bedeutung, mit der Windows 10 IoT die Digital I/Os des Raspberry bedienen kann. Hier kommen zwei Anforderungsfälle bzw. Performancefragen ins Spiel:

  • Mit welcher Frequenz kann ein Digitaleingang gelesen bzw. Digitalausgang geschrieben werden? Dies kann z.B. bei Verwendung von Pulsweitenmodulation PWM (der Raspberry PI hat keine Analogein- oder ausgänge) oder beim Sampling/Multiplexen von Eingängen interessant werden.
  • Wie lange dauert es minimal, bis auf einen Zustandswechsel eines Digitaleingangs reagiert wird? Diese Verzögerungszeit ist wichtig, um beispielsweise fest im Datenblatt definierte Timings eines Hardwarebausteins einhalten zu können.

Programmierung

Die Entwicklung eines Programms für Windows 10 IoT erfolgt über das Erstellen einer Universal Windows App. Hierfür wird das entsprechende Project Template im Visual Studio benutzt. Der Zugriff auf Hardwarekomponenten wird durch Hinzufügen einer Universal Extensions Referenz im Projekt auf die Windows IoT Extensions for the UWP zur Verfügung gestellt:

Add Reference

Für das Management der Digital I/Os sind unter Windows 10 IoT die Klassen GpioController und GpioPin vorgesehen. GpioController dient als übergeordnete Managementklasse dem Abfragen der verfügbaren GpioPins sowie deren Konfiguration als Ein- oder Ausgang. Das Lesen und Schreiben von  Signalen wird dann über GpioPin mit den Methoden Read und Write erledigt. Diese legen damit die im ersten Anforderungsfall beschriebene maximale Lese- und Schreibfrequenz fest. Zum Erkennen von Zustandsänderungen eines Eingangs stellt GpioPin das Event ValueChanged zur Verfügung. Die im zweiten Anforderungsfall dargestellte Verzögerungszeit ist also genau die Dauer, die vom Ändern des Signalzustands bis zum Erreichen der auf ValueChanged registrierten Eventhandlermethode verstreicht.

Das Scheitern eines Projektes des Autors führte dazu, einmal die Ausführungszeiten von Vorgängen mit Digital I/Os zu messen. Konkret gab es Probleme beim Einsatz zweier Hardwarekomponenten, die jeweils zu hohe Anforderungen an die digitale Signalverarbeitungsgeschwindigkeit stellten.

Im ersten Fall sollte ein VLSI Soundchip zur Wandlung eines analogen Stereo Audiosignals in digitale Wavedaten mit 44kHz / 16 Bits zum Einsatz kommen. Dies erforderte die zyklische Prüfung eines Eingangssignals des Chips pro übertragenem Byte der Wavedaten. Eine einfache Rechnung ergibt dann eine minimale Lesefrequenz des Steuersignals von 44kHz * 2 (Stereo) * 2 (16 Bits) = 176kHz. Anders herum ausgedrückt darf die maximale Dauer eines Lesevorgangs eines Eingangssignals im gegebenen Anwendungsfall 5,6µs nicht übersteigen. Um auf der sicheren Seite zu sein, sollte ein Lesen des Digitaleingangs noch deutlich schneller sein, damit noch Zeit für restliche Aufgaben wie die Bearbeitung der Wavedaten (Filtern, Wegschreiben, Konvertieren) zur Verfügung steht. Die Erwartung ist, dass dies auf einer 900MHz CPU kein Problem darstellen dürfte.

Messung

Die Messung der Ausführungsgeschwindigkeit kann mit der folgenden kurzen Methode durchgeführt werden:

   1: /// <summary>

   2: /// Perform GPIO reads in a close inner loop

   3: /// </summary>

   4: public async Task DoReads()

   5: {

   6:     // Get default GPIO controller

   7:     var gpioController = await GpioController.GetDefaultAsync();

   8:

   9:     // Open pin #6 for input 

  10:     using (var gpioReader = gpioController.OpenPin(6, GpioSharingMode.Exclusive)) {

  11:         gpioReader.SetDriveMode(GpioPinDriveMode.Input);

  12:         // Reset counter

  13:         this.PerformanceCounter.Reset();

  14:         // Do reads as long as measurement is active

  15:         while (this.isMeasurementActive) {

  16:             var value = gpioReader.Read();

  17:             this.PerformanceCounter.Counter++;

  18:         }

  19:     }

  20:     this.stopMeasurement.Set();

  21: }

Das Prinzip ist sehr einfach: In einer kurzen Schleife wird so schnell wie möglich ein Digitaleingang gelesen und ein Zähler (long) inkrementiert (Zeilen 15-18). Eine äußere Messmethode ermittelt dann in regelmäßigen Abständen die Inkrements pro Zeit. Dabei wird für die Zeitmessung die im Raspberry Pi mit High Precision implementierte StopWatch Klasse verwendet. Daraus kann direkt die Frequenz berechnet werden, mit der die Schleife läuft. Um den Einfluss des Messalgorithmus herauszurechnen, wurde zunächst die Schleife mit auskommentierter Zeile 16 ohne Lesen des Digitaleingangs verwendet. Anschließend wurde dann die echte Messung durchgeführt. Analog kann das Verfahren für das Schreiben eines Digitalausgangs angewendet werden. Die folgende Tabelle listet die Ergebnisse für verschiedene Versionen von Windows 10 IoT auf einem Raspberry Pi 2:

Ergebnisse
.

Windows 10 IoT Version Frequenz leer Dauer leer Frequenz Lesen Dauer Lesen Frequenz Schreiben Dauer Schreiben
1507 14MHz 70ns 41kHz 24µs 40kHz 25µs
1511 14MHz 70ns 167kHz 6µs 167kHz 6µs
1608 14Mhz 72ns 250kHz 4µs 222kHz 4,5µs

.
Bewertung

Zunächst ist festzustellen, dass der Einfluss der Messmethode zu vernachlässigen ist. Die Leerschleife läuft rund drei Größenordnungen schneller als unter Verwendung einer Messung. Dann ist zu beobachten, dass mit jeder Windows 10 IoT Version das Handling der Digital I/Os signifikant verbessert wurde. Besonders von Version 1507 auf 1511 konnte ein Performanceschub um grob den Faktor 4 erzielt werden. Alle Ergebnisse wurden übrigens im Debugmode gemessen. Das Kompilieren des Programms mit der native Toolchain, d.h. Erzeugen von nativem Arm Code, beschleunigt die Leerschleife um etwa den Faktor 4 auf 61MHz (16ns Zykluszeit), hat jedoch keinen messbaren Einfluss auf Schreiben oder Lesen via GpioPin. Hier spielt wohl letztlich die Implementierung im Kernel die entscheidende Rolle.

Damit besteht unter Version 1608 zum ersten Mal die Möglichkeit, das oben beschriebene Anwendungsszenario zu realisieren. Dennoch kann man die Messergebnisse insgesamt nur als sehr enttäuschend bezeichnen. Ähnliche Benchmarks unter anderen Betriebssystemen bei Verwendung nativen Codes zeigen ganz andere Werte: So ist unter Linux mit Hilfe von C Code und Einsatz der native Library eine Frequenz von 22MHz bei eine Zykluszeit von 45ns zu erreichen. Dies ist ca. 2 Größenordnungen oder um den Faktor 100 besser, als der mit der neuesten Windows 10 IoT und C# erreichbare Wert. Diese Werte eröffnen noch ganz andere Anwendungsmöglichkeiten und stellen die Verwendung von Windows 10 IoT für viele Hardwareprojekte komplett in Frage.

Es ist klar, dass Windows 10 IoT nicht den Anspruch hat, mit harten Echtzeitbetriebssystemen wie etwa LynxOS oder OS-9 zu konkurrieren. Jedoch werden die offensichtlich bestehenden Möglichkeiten der Raspberry Pi Hardware noch nicht einmal annähernd ausgeschöpft. Hier existiert also noch reichlich Optimierungspotential und –bedarf, damit auch Performance herausfordernde Projekte möglich werden.

 

Im zweiten Teil der Serie wird das Zeitverhalten von Windows 10 IoT bei Hardwareevents untersucht.

Sichere Erkennung von seriellen Ports

Bei der Realisierung hardwarenaher Funktionen ist des Öfteren die Kommunikation mit Geräten über einen seriellen Port zu implementieren. Im Zusammenhang mit virtuellen USB Ports, die jederzeit angesteckt oder abgezogen werden können, ist eine sichere Erkennung der im System vorhandenen seriellen Schnittstellen essentiell. Informationen zum seriellen Port und USB lassen sich z.B. auf Wikipedia unter Serielle Schnittstelle – Wikipedia und Universal Serial Bus – Wikipedia finden.

Auch im 21. Jahrhundert sind serielle Schnittstellen kein Relikt längst vergangener Tage, sondern sind häufig als Steuerungsmedium verschiedener Hardwaretypen gegenwärtig. So lassen sich viele UMTS-Sticks oder UMTS-Router nicht nur über eine Weboberfläche oder durch eine sonstige vom Hersteller bereitgestellte Software konfigurieren. Sie stellen zusätzlich Steuerungs- und Monitorfunktionen über eine virtuelle serielle USB Schnittstelle bereit. Oft ist dies die einzige Möglichkeit, über einen erweiterten AT-Befehlssatz Funktionen außerhalb der bereitgestellten UI zu erreichen. Eine Beschreibung des ursprünglich zur Steuerung von Modems geschaffenen AT-Befehlssatzes befindet sich auf AT-Befehlssatz – Wikipedia. Soll beispielsweise die eigene Applikation Änderungen der Signalstärke oder Verbindungsklasse (LTE, HSPA, etc.) eines UTMS-Sticks  visualisieren, ist die Einbindung der seriellen Ports in den Code gefordert.

Das .NET Framework gibt dem Entwickler für die Kommunikation über serielle Schnittstellen die Klasse System.IO.Ports,SerialPort an die Hand. Vorsichtig ausgedrückt ist diese Klasse nicht optimal implementiert und enthält eine Reihe von Fehlern oder Schwächen (siehe z.B. If you *must* use .NET System.IO.Ports.SerialPort – Sparx Blog). Bekanntermaßen sind z.B. das DataReceived Event(vgl. c# – SerialPort not receiving any data – Stack Overflow), die BytesToRead Property (SerialPort.BytesToRead returns 0 even though data in the buffer?) oder die ReadExisting (z.B. Serial Ports in C# ReadExisting() issue? – CodeProject) Methode problematisch. Die Close Methode zum Schließen des Ports erzeugt ein Deadlock, falls auf dem SerialPort Objekt noch ein Eventhandler registriert ist.

Die eigene Erfahrung zeigt, dass insbesondere im Zusammenhang mit virtuellen seriellen USB Ports, auch die statische Methode GetPortNames zur Abfrage der verfügbaren seriellen Schnittstellen teilweise keine korrekten Ergebnisse liefert. Wird ein Gerät bei aktivem geöffnetem Port vom USB abgezogen, erscheint es dennoch weiterhin bei Aufruf von GetPortNames als vorhandene Schnittstelle. Erneutes Einstecken führt dazu, dass es zweimal gemeldet wird. Wird es in einen anderen USB Anschluss eingesteckt, wird möglicherweise eine neue Schnittstelle (korrekt) gemeldet, wobei aber die vorher vorhandene weiterhin in der Auflistung von GetPortNames zu finden ist. Eventuell wird dieses Verhalten durch mangelhafte Treiber der virtuellen COM Ports hervorgerufen. Fakt ist jedenfalls, dass das String Array von COM Portnamen als Resultat dieser Methode nicht ungeprüft benutzt werden kann.

Besonders fatal ist dieses Verhalten von GetPortNames, wenn die Applikation das An- oder Abstecken von Hardware überwachen und bei neu erkannten Schnittstellen eine automatische Erkennung daran angebundener Geräte vornehmen soll. An der von GetPortNames erhaltenen Schnittstellenliste ist erfahrungsgemäß nur verlässlich, dass sie höchstens mehr, aber niemals weniger Portnamen enthält, als tatsächlich vorhanden sind.

Die Erkennung neuer Geräte bzw. ihr Verschwinden kann in Windows durch eine WMI Event Query überwacht werden:

   1: WqlEventQuery deviceArrivalQuery = new WqlEventQuery(

   2:     "SELECT * FROM Win32_DeviceChangeEvent WHERE EventType = 2");

   3: WqlEventQuery deviceRemovalQuery = new WqlEventQuery(

   4:     "SELECT * FROM Win32_DeviceChangeEvent WHERE EventType = 3");

   5:  

   6: this.arrival = new ManagementEventWatcher(deviceArrivalQuery);

   7: this.removal = new ManagementEventWatcher(deviceRemovalQuery);

   8:  

   9: this.arrival.EventArrived += this.ArrivalOnEventArrived;

  10: this.removal.EventArrived += this.RemovalOnEventArrived;

  11:  

  12: // Start listening for events

  13: this.arrival.Start();

  14: this.removal.Start();

In den Eventhandlern ArrivalOnEventArrived (Neues Gerät) und RemovalOnEventArrived (Gerät verschwunden) soll nun zuverlässig die Liste der verfügbaren seriellen Ports ermittelt werden. Nachgelagert kann die Applikation dann auf neu hinzugekommenen Ports eine Geräteerkennung ausführen. Bei entfernten Ports kann sie z.B. die Überwachung eines Gerätes beenden oder sonst geeignet reagieren. Dabei hat sich die folgende statische Methode zum Ermitteln der tatsächlich aktuell vorhandenen COM Ports bewährt:

   1: /// <summary>

   2: /// Detect system's valid serial ports

   3: /// </summary>

   4: /// <returns>

   5: /// String array of names of system's available serial ports

   6: /// </returns>

   7: public static string[] DetectSerialPorts()

   8: {

   9:     // NOTE: Due to bugs in the .NET serial port implementation it is necessary

  10:     // to check all ports availability by trying to open them.

  11:     string[] availableSerialPorts = SerialPort.GetPortNames();

  12:     List<string> testSerialPorts = availableSerialPorts.ToList();

  13:     List<string> checkedSerialPorts = new List<string>();

  14:     foreach (string checkPort in testSerialPorts.Distinct()) {

  15:         try {

  16:             Logger.Trace("Trying to detect serial port {0}", checkPort);

  17:             using (SerialPort testPort = new SerialPort(checkPort)) {

  18:                 testPort.Open();

  19:                 testPort.Close();

  20:             }

  21:             // If this line of code is reached, th serial port exists and 

  22:             // is not in use by any instance

  23:             checkedSerialPorts.Add(checkPort);

  24:         }

  25:         catch (InvalidOperationException) {

  26:             // Port exists:

  27:             // From MSDN documentation: The specified port on the current 

  28:             // instance of the SerialPort is already open.

  29:             Logger.Trace("Got InvalidOperationException on port {0}", checkPort);

  30:             checkedSerialPorts.Add(checkPort);

  31:         }

  32:         catch (UnauthorizedAccessException) {

  33:             // Port exists:

  34:             // From MSDN documentation: Access is denied to the port or the current

  35:             // process, or another process on the system, already has the 

  36:             // specified COM port open either by a SerialPort instance or in 

  37:             // unmanaged code.

  38:             Logger.Trace("Got UnauthorizedAccessException on port {0}", checkPort);

  39:             checkedSerialPorts.Add(checkPort);

  40:         }

  41:         catch (ArgumentOutOfRangeException) {

  42:             // Port exists:

  43:             // From MSDN documentation: One or more of the properties for this 

  44:             // instance are invalid. For example, the Parity, DataBits, or 

  45:             // handshake properties are not valid values; the BaudRate is less 

  46:             // than or equal to zero; the ReadTimeout or WriteTimeout property 

  47:             // is less than zero and is not InfiniteTimeout. 

  48:             Logger.Trace("Got ArgumentOutOfRangeException on port {0}", checkPort);

  49:             checkedSerialPorts.Add(checkPort);

  50:         }

  51:         catch (IOException) {

  52:             // Port does not exist and will not be added to the checked serial 

  53:             // ports list: From MSDN documentation: The port is in an invalid state

  54:             // or an attempt to set the state of the underlying port failed. 

  55:             Logger.Trace("Got IOException on port {0}", checkPort);

  56:         }

  57:         catch (Exception exc) {

  58:             Logger.ErrorException("Unexpected error caught: ", exc);

  59:         }

  60:     }

  61:     availableSerialPorts = checkedSerialPorts

  62:         .Distinct()

  63:         .OrderByDescending(p => p)

  64:         .ToArray();

  65:  

  66:     return availableSerialPorts;

  67: }

Dazu wird jeder Port geöffnet und sofort wieder geschlossen (Zeilen 21-23). Funktioniert das fehlerfrei ist der Port vorhanden und wird der Liste der geprüften Ports hinzugefügt (Zeilen 19-22). Im Fehlerfall ist die geworfene Exception auszuwerten: Nach MSDN Dokumentation werden InvalidOperationException, UnauthorizedAccessException und ArgumentOutOfRangeException unter verschiedenen Umständen geworfen. Alle drei treten jedoch nur bei Existenz des Ports auf. Daher wird im jeweiligen Catch Block die Liste der geprüften Ports um den aktuellen Testkandidaten erweitert (Zeilen 25-50). Tritt hingegen eine IOException auf, ist der Port nicht vorhanden oder verfügbar und wird nicht der Liste der geprüften Ports hinzugefügt (Zeilen 51-59).

Fazit: Die sichere Kommunikation mit Geräten über den seriellen Port ist mit .NET Mitteln möglich. Der Entwickler sollte aber mit Überraschungen rechnen und die implementierten Funktionen am besten mit einer Vielzahl an Gerätekombinationen ausgiebig testen.

Entwicklungsumgebung für SharePoint 2013 unter Visual Studio 2012 einrichten

Das Aufsetzen einer Entwicklungsumgebung für SharePoint 2013 unter VS 2012 hält einige mögliche Stolperfallen bereit. Es nach wie vor nicht möglich, die Entwicklung remote durchzuführen und somit weiterhin notwendig, den kompletten SharePoint Server lokal auf der Entwicklungsmaschine zu installieren.

Nach der Installation von Visual Studio 2012 mit Update 1 sind zunächst keine Projektvorlagen zur Entwicklung von SharePoint 2013 Apps oder Web Parts vorhanden:

clip_image002

Für die Entwicklung von SP 2013 werden die Microsoft Office Developer Tools for Visual Studio 2012 benötigt. Der erforderliche Web Platform Installer der Developer Tools kann hier heruntergeladen  werden.

Nach Start des Web Installers wird ein zu installierendes Item angezeigt. Ein Klick auf Details zeigt die benötigten Komponenten an:

clip_image004

Nach Klick auf den Button Install kann die Installation je nach auf der Entwicklungsmaschine bereits vorhandenen Komponenten (z.B. VS 2010) scheitern. Die Installation endet in diesem Fall mit folgender Fehlermeldung:

clip_image006

Grund dafür ist eine vorhandene Installation der Microsoft Exchange Web Services Managed API 1.2.

Im Visual Studio 2012 sind jetzt zwar die Projektvorlagen für die SharePoint 2013 Entwicklung vorhanden:

clip_image008

Der Versuch, beispielsweise ein SP 2013 Visual Web Part Projekt anzulegen, wird jedoch mit der Fehlermeldung An error occurred whilst trying to load some required components, Please ensure the following prerequisite components are installed. Microsoft Web Developer Tools and Microsoft Exchange Web Services quittiert.

Das Problem kann wie folgt behoben werden: In den Programs and Features Systemeinstellungen werden die Microsoft Exchange Web Services Managed API 1.2 und die neu installierten Microsoft Office Developer Tools for Visual Studio 2012 – Preview 2 – ENU manuell deinstalliert. Danach wird erneut der Web Platform Installer ausgeführt. Diesmal ist er in der Lage, die korrekte Version der Microsoft Exchange Web Services Managed API 2.0 und der Office Developer Tools zu installieren. Anschließend ist es möglich, in Visual Studio 2012 SharePoint 2013 Projekte anzulegen und zu bearbeiten.

Ein letzter Tipp: Sollte bei Anlegen eines neuen SP 2013 Web Part Projekts nach Klick auf Validate die folgende oder eine ähnliche Fehlermeldung erscheinen, obwohl der Entwickler alle benötigen Rechte auf dem Testportal hat, lohnt es sich, die erforderlichen Rechte auf dem Sql Server nachzuprüfen und ggfs. einzustellen. Der Entwicklungsaccount muss db_owner auf der SharePoint Content Datenbank sein.

clip_image009