Beim Parsen bzw. Zusammenstellen des Response-Streams bin ich auf ein Problem gestoßen. Es war auf einmal gar nicht mehr so leicht, die beiden Response Bytes an die richtige Stelle im Stream zu schreiben. Bei gültigen Responses war dies noch eine leichte Übung. Was aber macht man, wenn das angeforderte Command nicht unterstützt wird und man so nicht unterscheiden kann, ob es sich um ein Connection-Required Command oder um ein Connection-Not-Required Command handelt? Durch diesen Unterschied würde sich bei der aktuellen Definition die Response-Bytes um einige Stellen verschieben.
Diese Schwachstelle im Protokoll werde ich ausmerzen, indem ich bei Connection-Required Commands die Response-Bytes vor den Connection Identifier setze.
Eine weitere Anpassung gibt es bei allen enumerierbaren Datentypen. Diese bekommen die Anzahl der Bytes im Stream als ushort vorgesetzt. Somit vereinfacht sich das Parsen der Request und Response Streams noch einmal.
Die gesamte Dokumentation ist hier zu finden:
https://jan-schubert.github.io/HomeAutomation/
Teil 1 der Serie: Raspberry Pi als Home Server
Teil 2 der Serie: Raspberry Pi als Home Server
Teil 3 der Serie: Raspberry Pi als Home Server
xp-development
Alles zu C#, WPF, MVVM, Windows, .NET und anderen Themen.
Freitag, 5. April 2019
Mittwoch, 9. Januar 2019
Raspberry Pi als Home Server Teil 3
Um mit dem Raspberry kommunizieren zu können, habe ich erstmal ein einfaches binäres Protokoll definiert.
Folgende Vorraussetzungen gelten:
Zu den Commands, die keine Anmeldung benötigen gehört bspw. das Connect Command. Folgend der Bytestream für dieses Command.
Es gibt Common Response Codes und Command spezifische. Folgend die Common Response Codes bei denen das erste Byte immer FF ist:
Die gesamte Dokumentation ist hier zu finden:
https://jan-schubert.github.io/HomeAutomation/
Teil 1 der Serie: Raspberry Pi als Home Server
Teil 2 der Serie: Raspberry Pi als Home Server
Folgende Vorraussetzungen gelten:
- Die Kommunikation läuft über TCP/IP.
- Alle Ganzzahlen werden dabei im Little-Endian Format übertragen.
- Strings werden als Unicode übertragen.
- Alle Connections nach 5 Minuten ohne Kommunikation beenden.
- Bei allen gestarteten Transaktionen wird nach 15 Sekunden ohne Kommunikation ein Rollback ausgeführt.
Zu den Commands, die keine Anmeldung benötigen gehört bspw. das Connect Command. Folgend der Bytestream für dieses Command.
00 01 00 00 00 00 00 00 00 AF FE
- Das erste Byte 00 steht für die Protokollversion und wird vorerst immer 00 sein. Dies habe ich eingeführt damit ich später, wenn ich merke das ich Anpassungen bzw. Erweiterungen im Protokoll benötige, dieses einfach unter einer neuen Versionsnummer erweitern kann. Dann müsste lediglich der Client und der Server verschiedene Protokollversionen unterstützen.
- Die nächsten vier Bytes 01 00 00 00 stehen für den Request Type und geben an, was eigentlich gemacht werden soll.
- Die nächsten zwei Bytes (ushort) sind ein Counter, der bei jeder Anfrage hochgezählt wird und in der Antwort vom Server zurückgeben wird. Ob dieser Counter wirklich benötigt wird, ist mir noch nicht ganz klar. Es gibt Vor- und Nachteile, die ich noch gegeneinander abwägen muss. Ggf. wird der Counter in der endgültigen Version des Protokolls 00 nicht mehr vorhanden sein.
- Die nächsten zwei Bytes (ushort) sind die Länge der Datenbytes in diesem Beispiel 00 00 und somit gibt es auch keine Daten.
- Die letzten beiden Bytes ist die CRC Checksumme AF FE. Diese wird in meinen Beispielen immer mit AF FE angegeben, in der eigentlichen Implementierung dann aber natürlich berechnet.
Der dazugehörige Response hat lediglich den Unterschied, dass nach dem Counter 2 Bytes als Response Codes dienen im folgenden Beispiel 00 00:
00 01 00 00 00 00 00 00 00 00 00 AF FE
Es gibt Common Response Codes und Command spezifische. Folgend die Common Response Codes bei denen das erste Byte immer FF ist:
- wrong crc FF 01
- not connected FF 02
- transaction required FF 03
- unknown command FF 04
- corrupt data FF 05
- not supported protocol version FF 06
Die gesamte Dokumentation ist hier zu finden:
https://jan-schubert.github.io/HomeAutomation/
Teil 1 der Serie: Raspberry Pi als Home Server
Teil 2 der Serie: Raspberry Pi als Home Server
Mittwoch, 5. Dezember 2018
Raspberry Pi als Home Server Teil 2
Nachdem ich das Grundgerüst zum Parsen (RequestParser und ResponseParser) und zum Zusammenstellen (RequestBuilder und ResponseBuilder) fertiggestellt und zeitgleich das Command für das Connect implementiert habe, habe ich damit begonnen den Server für den Raspberry zu schreiben.
Das Projekt Template für Visual Studio 2017 ist unter folgendem Link zu finden:
Nach dem Anlegen des Projektes habe ich eine kurze sehr simple Implementierung geschrieben und im nächsten Zug versucht die App auf meinen Raspberry zu veröffentlichen. Die Zielplattform musste ich korrekterweise noch auf ARM stellen, konnte nach dem Deployen aber direkt via Remote Debugging die App debuggen.
Die Background Task:
public sealed class StartupTask : IBackgroundTask { private static readonly ILogger Log = LogManager.GetCurrentClassLogger(); private BackgroundTaskDeferral _deferral; public async void Run(IBackgroundTaskInstance taskInstance) { Log.Info("-----------------------------------------------------------------------------"); Log.Info($"Start server {typeof(StartupTask).GetTypeInfo().Assembly.GetName().Version}."); _deferral = taskInstance.GetDeferral(); taskInstance.Canceled += TaskInstanceCanceled; await new Bootstrapper().RunAsync(); } private void TaskInstanceCanceled(IBackgroundTaskInstance sender, BackgroundTaskCancellationReason reason) { Log.Info($"Stop server {typeof(StartupTask).GetTypeInfo().Assembly.GetName().Version}."); _deferral.Complete(); } }
Release Notes für Windows IoT Raspberry Pi 3 B+ Insider Preview
Mittwoch, 14. November 2018
Raspberry Pi als Home Server Teil 1
Das eigene Heim wird heutzutage immer Smarter. Dabei treffen unterschiedlichste Interessen und verschiedenste Protokolle aufeinander. Von der Lichtsteuerung über ZigBee, hin zur Rolladensteuerung über KNX, bis hin zur Sensorüberwachung mit Enocean. Alles soll miteinander interagieren können, am Besten über eine und nicht über verschiedene Apps. Zusätzlich sollte eine Steuerung auch mit Alexa, Cortana oder dem Google Assistant möglich sein.
Diesem Thema möchte ich mich in den nächsten Wochen und Monaten widmen und habe damit auch schon begonnen.
Ich habe mir einen Raspberry Pi 3 B+ bestellt und Windows 10 IoT installiert. Danach habe ich bei GitHub ein neues Projekt angelegt und begonnen ein Protokoll für die Kommunikation zwischen Raspberry und der App zu definieren. Parallel dazu habe ich ein .NET Standard Projekt erstellt und erste Klassen angelegt (ConnectRequestBuilder, ResponseParser, ...).
Wie immer gilt: TDD.
Freitag, 6. Juni 2014
Lokalisierung einer Windows 8 Store App
Die Lokalisierung in Windows Store Apps funktioniert denkbar einfach. Soll Beispielsweise der Text eines TextBlock's in verschiedenen Sprachen dargestellt werden, so muss diesem Control lediglich eine x:Uid zugewiesen werden. Zum Beispiel folgendermaßen:
Jetzt muss in dem Projekt ein Ordner angelegt werden, der Strings heißt. In diesen kommen weitere Verzeichnisse, die den Namen der gewünschten Sprache im BCP-47 Format haben. Z.B. de-DE oder nur de oder en. Jedes dieser Verzeichnisse bekommt eine Resources.resw Datei.
Für unser Beispiel legen wir die Resource MyTextBlock.Text mit dem Wert "Hallo Welt!" im Deutschen und im Englischen das gleiche mit dem Wert "Hello World!" an. Das Pattern ist also Uid.PropertyName und zeigt, dass es nicht nur möglich ist, den anzuzeigenden Text, sondern auch andere Properties eines Controls zu setzen.
Das war es schon, wird die Anwendung gestartet, wird der Text in der jeweiligen Standard Sprache angezeigt.
<Page
x:Class="App2.MainPage"
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">
<Grid HorizontalAlignment="Center" VerticalAlignment="Center">
<TextBlock x:Uid="MyTextBlock" />
</Grid>
</Page>
Jetzt muss in dem Projekt ein Ordner angelegt werden, der Strings heißt. In diesen kommen weitere Verzeichnisse, die den Namen der gewünschten Sprache im BCP-47 Format haben. Z.B. de-DE oder nur de oder en. Jedes dieser Verzeichnisse bekommt eine Resources.resw Datei.
Für unser Beispiel legen wir die Resource MyTextBlock.Text mit dem Wert "Hallo Welt!" im Deutschen und im Englischen das gleiche mit dem Wert "Hello World!" an. Das Pattern ist also Uid.PropertyName und zeigt, dass es nicht nur möglich ist, den anzuzeigenden Text, sondern auch andere Properties eines Controls zu setzen.
Das war es schon, wird die Anwendung gestartet, wird der Text in der jeweiligen Standard Sprache angezeigt.
Donnerstag, 3. April 2014
XP sagt auf Wiedersehen
Microsoft hat es schon vor einiger Zeit angekündigt und nun ist es soweit! Windows XP wird in 5 Tagen und 6 Stunden den Support einstellen!
Spätestens jetzt sollten auch die letzten Firmen und Privatpersonen Abschied nehmen und sich nach etwas neuem umschauen. Denn ohne Sicherheitsupdates und Hotfixes wird es auf kurz oder lang Probleme mit dem System geben.
Den aktuellen Countdown gibt es direkt von Microsoft hier!
Spätestens jetzt sollten auch die letzten Firmen und Privatpersonen Abschied nehmen und sich nach etwas neuem umschauen. Denn ohne Sicherheitsupdates und Hotfixes wird es auf kurz oder lang Probleme mit dem System geben.
Den aktuellen Countdown gibt es direkt von Microsoft hier!
Donnerstag, 13. März 2014
Asynchrone Commands testen leicht gemacht
Was war das immer für ein Krampf die Execute Methode eines Commands zu testen, das asynchonen Code ausführt. Das hat aber schon seit geraumer Zeit ein Ende. NUnit unterstützt seit der Version 2.6.2 das async await, das mit C# 5.0 der Entwicklerwelt zur Verfügung gestellt wurde.
Für alle Commands, die längere Aktionen ausführen sollen, nutze ich die folgende Klasse:
Die Benutzung ist einfach, so wie man es vom async/await bzw. von den synchronen Commands her kennt. Hier ein Beispiel in dem im MainViewModel drei Sekunden nach dem Ausführen des Commands eine Nachricht gesetzt werden soll.
Diese Implementierung hat den großen Vorteil, das die Anwendung nicht einfriert nachdem der Button geklickt wurde.
Und nun zum eigentlichen Thema: Testen!
Wie schon geschrieben, unterstützt NUnit seit der Version 2.6.2 das Ausführen von asynchronen Tests. Dementsprechend einfach ist es das Command zu testen:
Hier der Sourcecode mit Visual Studio 2012 Solution.
Für alle Commands, die längere Aktionen ausführen sollen, nutze ich die folgende Klasse:
public class AsyncCommand<TExecuteArg, TCanExecuteArg> : ICommand
{
public event EventHandler CanExecuteChanged
{
add
{
if (_canExecuteMethod == null)
return;
CommandManager.RequerySuggested += value;
}
remove
{
if (_canExecuteMethod == null)
return;
CommandManager.RequerySuggested -= value;
}
}
public AsyncCommand(Func<TExecuteArg, Task> executeMethod, Func<TCanExecuteArg, bool> canExecuteMethod = null)
{
if (executeMethod == null)
throw new ArgumentNullException("executeMethod");
_executeMethod = executeMethod;
_canExecuteMethod = canExecuteMethod;
}
public Task Execute(TExecuteArg arg)
{
return _executeMethod(arg);
}
public bool CanExecute(TCanExecuteArg arg)
{
return _canExecuteMethod == null || _canExecuteMethod(arg);
}
bool ICommand.CanExecute(object parameter)
{
return CanExecute((TCanExecuteArg) parameter);
}
async void ICommand.Execute(object parameter)
{
await Execute((TExecuteArg) parameter);
}
private readonly Func<TExecuteArg, Task> _executeMethod;
private readonly Func<TCanExecuteArg, bool> _canExecuteMethod;
}
Wie man sehen kann, ist die Execute Methode mit dem async Keyword versehen. Alles was in der Execute Action ausgeführt wird, kann also asynchron sein.Die Benutzung ist einfach, so wie man es vom async/await bzw. von den synchronen Commands her kennt. Hier ein Beispiel in dem im MainViewModel drei Sekunden nach dem Ausführen des Commands eine Nachricht gesetzt werden soll.
public class MainViewModel : ViewModelBase
{
public string Message
{
get { return _message; }
set
{
_message = value;
NotifyPropertyChanged("Message");
}
}
public AsyncCommand<object, object> LongRunningCommand { get; private set; }
public MainViewModel()
{
LongRunningCommand = new AsyncCommand<object, object>(OnLongRunningExecute);
}
private async Task OnLongRunningExecute(object args)
{
await Task.Run(() => Thread.Sleep(TimeSpan.FromSeconds(3)));
Message = "Test message";
}
private string _message;
}
Die OnLongRunningExecute Methode ist hier auch mit dem async Keyword versehen und in der Methode werden dann die drei Sekunden gewartet bevor die Nachricht gesetzt wird.Diese Implementierung hat den großen Vorteil, das die Anwendung nicht einfriert nachdem der Button geklickt wurde.
Und nun zum eigentlichen Thema: Testen!
Wie schon geschrieben, unterstützt NUnit seit der Version 2.6.2 das Ausführen von asynchronen Tests. Dementsprechend einfach ist es das Command zu testen:
[TestFixture]
public class LongRunningCommand
{
[Test]
public async void Usage()
{
var viewModel = new MainViewModel();
await viewModel.LongRunningCommand.Execute(null);
viewModel.Message.Should().Be("Test message");
}
}
Benutzt man eine ältere Version von NUnit, läuft der Test in weniger als einer Sekunde durch und wird beim Assert schief laufen.Hier der Sourcecode mit Visual Studio 2012 Solution.
Abonnieren
Posts (Atom)