Tag Archives: C#

Hacking the Lego Mindstorms EV3

My brand-new Lego EV 3 Mindstorms arrived just a few weeks ago. Immediately I dived into unveiling its secrets. I was more than curious what I might be able to do with this robotics kit besides the official software. Unfortunately, at that time there was only little information available about the detailed specifications. The most valuable information is hidden in the Mindstorms operating system! Thanks to Lego, the source code is available at GitHub. If you are not familiar with the structure of Linux, the programming language C and alike, don´t bother. The leJOS-Team is working on a Java friendly OS and it seems some people are working on a Node.js package running JavaScript on the brick (e.g. https://github.com/clebert/ev3/issues/1). If remote controlling is suitable for you, the .Net based MonoBrick is worth a look.

lego_mdinstorms_ev3

If you like to cope with the Mindstoms internals yourself, you only need the file c_com\c_com.h to get a basic understanding of the Mindstorms capabilities. There you´ll find the structure of the used communication protocol (unfortunately, there is no official documentation on the EV3 protocol besides the source code yet). The protocol is not that hard, but a little tricky: It´s just sending byte arrays with commands and their corresponding values back and forth. So, if you want to try it yourself, it basically works like this…

First you need to connect to the Mindstorms. With Windows 8.1 (WinRT) and Bluetooth it looks like:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
public async void Connect()
{
 
    IReadOnlyList<deviceinformation> deviceInfoCollection = null;
 
    deviceInfoCollection = await DeviceInformation.FindAllAsync(
        RfcommDeviceService.GetDeviceSelector(RfcommServiceId.SerialPort)
    );
 
    DeviceInformation deviceInfo = deviceInfoCollection.FirstOrDefault(c => c.Name.Contains("EV3"));
 
    RfcommDeviceService rfcommService;
    rfcommService = await RfcommDeviceService.FromIdAsync(deviceInfo.Id);
 
    if (rfcommService == null)
    {
        // Message and open control settings for pairing bluetooth device here.
        return;
    }
 
    // Create a socket and connect to the target. 
    socket = new StreamSocket();
 
    await socket.ConnectAsync(
                rfcommService.ConnectionHostName,
                rfcommService.ConnectionServiceName,
                SocketProtectionLevel.BluetoothEncryptionAllowNullAuthentication);
 
    // Create data writer and reader to communicate with the device.
    dataWriter = new DataWriter(socket.OutputStream);
    dataReader = new DataReader(socket.InputStream);
 
}

With Windows Phone 8 you need to pair the device a little differently:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
public async void Connect()
{
 
    PeerFinder.AlternateIdentities["Bluetooth:PAIRED"] = "";
    IReadOnlyList<PeerInformation> devices = null;
 
    try
    {
        // Could fail if there is no device paired.
        devices = await PeerFinder.FindAllPeersAsync();
    }
    catch
    {
    }
 
    if (devices == null || devices.Count == 0)
    {
        MessageBox.Show("No Bluetooth devices are paired, please pair your Lego Mindstorms EV3.");
        await Windows.System.Launcher.LaunchUriAsync(new Uri("ms-settings-bluetooth:"));
        return;
    }
 
    PeerInformation peerInfo = devices.FirstOrDefault(c => c.DisplayName.Contains("EV3"));
    if (peerInfo == null)
    {
        MessageBox.Show("No paired Lego Mindstorms EV3 was found, please pair your Mindstorms.");
        await Windows.System.Launcher.LaunchUriAsync(new Uri("ms-settings-bluetooth:"));
        return;
    }
 
    // Create a socket and connect to the target. 
    socket = new StreamSocket();
    await s.ConnectAsync(peerInfo.HostName, peerInfo.ServiceName);
 
    // Create data writer and reader to communicate with the device.
    dataWriter = new DataWriter(socket.OutputStream);
    dataReader = new DataReader(socket.InputStream);
 
}

Please note, it hasn´t to be Windows and C#. You can use every environment as long as it connects to the EV3 (even Wifi is possible).

Once the connection is established, byte-arrays have to be constructed. The structure of these arrays is a little odd, but we have to deal with it. The first two bytes of the array define the array length (therefore, the array always has two more bytes than defined here):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// Command length.
int length = 13;
// Needs two more bytes for defining the length. 
byte[] data = new byte[length+2]; 
 
// Byte 0 – 1: Command size.
// Minor byte of length.
data[0]  = (byte)(length & 0x00ff);
// Major byte of length - if not longer than 254 it's zero.
data[1]  = (byte)((length & 0xff00) >> 2);
 
// Byte 2 – 3: Message counter (seems not be needed so far).
data[2] = (byte)0x00;
data[3] = (byte)0x00;

Now it gets a little bit more sophisticated, because there are different types of commands, which you can specify here: Some commands are system commands to control the more overall system functionalities, some commands are direct commands related to functionality which is controlled within the bricks virtual machine. Depending on the command and it´s parameters, the structure of the byte array slightly varies. In this explanation, I use a direct command to drive the robot:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
// Byte 4: Command type and if it´s sending a reply.
data[4] = (byte)CommandType.DIRECT_COMMAND_NO_REPLY;
 
// Byte 5 - n: Dependent on command type.
// Byte 5 - 6: Number of global (for optional response) and local variables. Variables are compressed:
// 
//   Byte 6    Byte 5
//  76543210  76543210
//  --------  --------
//  llllllgg  gggggggg
 
int globalVariablesCount = 0;
int localVariablesCount = 0;
data[5] = (byte)(globalVariablesCount & 0xFF);
data[6] = (byte)((localVariablesCount << 2) | (globalVariablesCount >> 8));
 
// Byte 7: Byte code for a command followed by its parameters.
data[7]  = (byte)Command.OutputSpeed;
 
// The first parameter describes the daisy chain layer if you use connected bricks.
data[8]  = (byte)DaisyChainLayer.EV3;
 
// The next parameter for this command describes the output channels as a bit field.
data[9]  = (byte)(OutputPort.B | OutputPort.C);
 
// And the last parameter is for the speed. Because EV3 supports different types of values, the type has to be set first followed by its value.
data[10] = (byte)ParameterFormat.LONG | (byte) LongParameterFollowFormat.ONE_BYTE;
data[11] = speed;
 
// After setting the speed, the motors have to be started now.
data[12] = (byte)Command.OutputStart;
// The first parameter again describes the daisy chain layer if you use connected bricks.
data[13] = (byte)DaisyChainLayer.EV3;
// The next parameter for this command agein describes the output channels as a bit field.
data[14] = (byte)(OutputPort.B | OutputPort.C);

The last and easiest step is to send everything to the robot:

1
2
dataWriter.WriteBytes(data);
await dataWriter.StoreAsync();

Reading a response that could include sensor data and alike works pretty much the same:

1
2
3
4
5
6
7
8
9
10
11
12
// Get the raw response and read the first two bytes
byte[] response = new byte[2];
await dataReader.LoadAsync(2);
dataReader.ReadBytes(response);
 
// Get the response length out of the first two bytes
expectedlength = (ushort)(0x0000 | response[0] | (response[1] << 2));
 
// Read the full response and the payload
byte[] payload = new byte[expectedlength];
await dataReader.LoadAsync(expectedlength);
dataReader.ReadBytes(payload);

Finally, I was somewhat proud to discover this (even if it´s not that hard). But just before I had the time to clean up the code, a team with Brian Peek (known for his awesome Coding4Fun articles) released a library with all this functionality (and even more). Therefore, I stopped my research and recommend this library, now.

Send SMS mit dem Developer Garden SDK

Die Global SMS API zum Senden und Empfangen von Kurznachrichten kann auf verschiedene Arten genutzte werden. Letztendlich mündet dann zwar alles meist in JSON/XML über REST, aber wer das nicht mag, kann dank der Developer Garden SDKs vorbereitete Bibliotheken nutzen. Diverse Programmierwelten werden unterstützt, darunter Java, .NET, PHP, Objective-C und Python. Diese Bibliotheken verstecken auch viel Komplexität – beispielsweise die Abläufe bei der Authentifizierung – und erleichtern so den Einstieg. Außerdem hilft der bereitgestellte Quellcode, die Funktionsweise der API besser zu verstehen, möchte man doch einmal Low-level direkt mit der RESTful API arbeiten.

Download und Doku

global_sms_02 (Time 0_01_08;07)

Die SDKs sowie die Dokumentation stehen im Developer Garden zum Download bereit.

global_sms_02 (Time 0_01_33;29)

Natürlich ist der letzte Stand der Dokumentation als HTML dort auch online einsehbar.

Ein kleiner Tipp am Rande: Die Global SMS API folgt einem Anbieter-übergreifenden Standard namens One API der GSM Association. Bei der GSMA handelt es sich um die 1987 gegründete Industrievereinigung der GSM-Mobilfunkanbieter mit mittlerweile mehr als 800 Mobilfunkanbieter. Dort ist unter http://www.gsma.com/oneapi/sms-restful-netapi auch eine weitere hilfreiche Dokumentation zum Thema SMS API erhältlich.

global_sms_02 (Time 0_02_02;18)

Nachdem die Entscheidung für ein SDK gefallen ist (hier in diesem Beispiel .Net) muss die darin enthaltene Funktionalität noch bereitgestellt werden. Da sich im SDK der Quellcode befindet, kann wahlweise dieser eingebunden oder eine Bibliothek erzeugt werden. So oder so muss unter Windows das heruntergeladene ZIP-Archiv noch entsperrt (dies gelingt am leichtesten über das Kontextmenü der Datei im Bereich Eigenschaften) und entpackt werden.

global_sms_02 (Time 0_02_33;29)

Einmal entpackt, kann nun der Quellcode, der auch zahlreiche Beispiele umfasst, eingesehen werden. Die relevante Projektmappe (Solution) für die Bearbeitung mit Visual Studio ist Telekom.sln.

global_sms_02 (Time 0_02_48;10)

Falls man in seine eigenen Projekte nicht immer den ganzen Quellcode einbinden möchte, kann nun innerhalb von Visual Studio die Bibliothek (DLL) erstellt werden, indem man das Projekt einmal „baut“ (Menü Build -> Build Solution). Standardmäßig findet sich die fertige Bibliothek dann im Quellcodeverzeichnis unter src/Telekom/bin/Debug. Neben der Telekom.dll liegt dort die ebenfalls notwendige Bibliothek Newtonsoft.Json.dll für die Verarbeitung von JSON.

Aktivierung und Zugangsdaten

global_sms_01 (Time 0_00_54;00)

Doch bevor es nun ans Programmieren einer eigenen Anwendung geht, muss man sich erst einmal im Developer Garden anmelden und die Global SMS API aktivieren.

global_sms_01 (Time 0_02_00;02)

Letzteres gelingt nach erfolgreicher Anmeldung durch Klick auf „Mein Konto“ und die anschließende Aktivierung von Global SMS API innerhalb der API-Verwaltung. Hier werden übrigens auch die Rufnummern für den Versand und Empfang verwaltet.

global_sms_01 (Time 0_03_18;14)

In der Applikationsverwaltung wird nun – als letzter Schritt bevor es an die Programmierung geht – eine neue Applikation erstellt und dort die Global SMS API verfügbar gemacht.

global_sms_01 (Time 0_04_12;26)

Hier finden sich auch die für die Nutzung der API wichtigen Angaben Scope, Client ID und Sicherheitstoken. Für die Programmierung werden diese unbedingt gebraucht.

Programmierung

global_sms_02 (Time 0_03_54;05)

Die Nutzung der Global SMS API ist wie hier in einer WPF Anwendung (oder sonst einem Anwendungstypen aus der .Net-Welt wie z. B. Windows Forms) nun sehr einfach. Alles was wir dafür brauchen sind die Bibliotheken und die Nutzungsdaten (Scope, Client ID und Sicherheitstoken).

global_sms_02 (Time 0_04_30;16)

Als erstes füge n wir die beiden Bibliotheken Telekom.dll und Newtonsoft.Json.dll dem Projekt hinzu. Sollte die Datei Telekom.dll sich nicht im SDK-Verzeichnis unter src/Telekom/bin/Debug finden, dann muss diese wie oben beschrieben noch erzeugt werden.

global_sms_02 (Time 0_05_17;21)

Nun legen wir eine Klasse mit den Nutzungsdaten an, um Zugriff auf die API zu erhalten. Die Nutzungsdaten finden sich wie oben beschreiben für jede Anwendung innerhalb Ihres Developer Garden Kontos (My Account) in der Applikationsverwaltung unter https://www.developergarden.com/my-account/application-management/.

global_sms_02 (Time 0_06_30;13)

Als nächstes legen wir ein Interaktionselement fest, das den SMS-Versand auslöst. Hier eine Schaltfläche, die durch Klicken die notwendigen Befehle startet.

global_sms_02 (Time 0_12_15;07)

Innerhalb der Ereignismethode für die Schaltfläche wird zuerst die Authentifizierung unter Angabe der Nutzungsdaten durchgeführt:

1
2
TelekomOAuth2Auth authentication = new TelekomOAuth2Auth(Credentials.CLIENT_ID, Credentials.CLIENT_SECRET, Credentials.SCOPE);
authentication.RequestAccessToken();

Als nächstes müssen die Parameter für die SMS festgelegt werden. Darunter die Rufnummer des oder der Empfänger als Liste, die eigentliche Nachricht und die Absenderadresse – sofern noch keine validierte Rufnummer verfügbar ist, kommt als Absender 0191011 zum Einsatz. Andernfalls die gewünschte Rufnummer, diese dann aber mit dem Prefix „tel:“:

1
2
3
4
5
6
List receiverNumbers = new List();
receiverNumbers.Add("tel:+4915112345678");
SendSmsRequest request = new SendSmsRequest();
request.Numbers = receiverNumbers;
request.Message = "Developer Garden rocks!";
request.SenderAddress = "0191011";

Und im letzten Schritt wird die API noch aufgerufen (in diesem Beispiel durch einen synchronen Aufruf, es kann aber auch asynchron gearbeitet werden). Die Angabe ServiceEnvironment.Sandbox steht übrigens für die kostenlose aber auf 10 SMS am Tag beschränkte Nutzung:

1
2
SendSmsClient client = new SendSmsClient(authentication, ServiceEnvironment.Sandbox);
SmsResponse response = client.SendSms(request);

In der Code-Behind-Datei legen wir noch die notwendigen Usings zum Einbinden der Namensräume fest, sofern das nicht bereits automatisch geschehen ist:

1
2
3
4
5
6
using System.Collections.Generic;
using Telekom.Common;
using Telekom.Common.Auth;
using Telekom.Common.Model;
using Telekom.SendSms;
using Telekom.SendSms.Model;

global_sms_02 (Time 0_12_29;28)

Das war es soweit. Starten der Anwendung und das anschließende Klicken auf die Schaltfläche sollte nun eine SMS versenden. Sofern wie in der Abbildung ein Unterbrechungspunkt gesetzt und der Debugger aktiv ist, kann man auch den erfolgreiche Aufruf der API sehen. Übrigens gibt es im Developer Garden auch ein Forum für Ideen und Fragen zum Thema. Viel Spaß…

Buchtipp: Durchstarten mit Raspberry Pi

Eine große Stärke des Einplatinen-Computers Raspberry Pi sind die vielfältigen Einsatz- und Erweiterungs-Möglichkeiten. Doch wie legt man eigentlich los? Erik Bartmann beantwortet diese Frage und hilft beim „Durchstarten mit Raspberry Pi“.

Unterstützt durch ein ansprechendes und mehrfarbiges Layout liest sich das Buch gut – sofern man die eher flapsige Art des Autors mag. Klasse sind die zahlreichen Hinweise, die typische Fragen und Stolperfalle hervorheben und anschaulich erklären. Verwirrend ist jedoch der Wechsel zwischen virtualisierten Linux-Umgebungen und dem echten Raspberry Pi. Zumal auch mehrere Betriebssystem-Versionen wie Debian Squeeze und das neuere Debian Wheeze im Buch vorkommen. In Anbetracht des frühen Erscheinungstermins des Buches ist klar, dass die neueste Hardware-Revision fehlt: Hier ist unbedingt Vorsicht geboten, wenn man sich mit den Hardware-nahen Beispielen im Buch befasst. Aber der Autor hat darauf reagiert und bereits einen entsprechenden Artikel (PDF) verfasst. Gut gefällt der Rundumschlag zu Linux, inklusive Kommandozeile und der grafischen Benutzungsoberfläche. Das nötigste wird bereits im Buch vermittelt und am Ende gibt es noch einmal ein hilfreiches Zusatzkapitel mit Linux-Grundlagen.

Der umfangreiche Einstieg in die Raspberry Pi Welt umfasst neben Installation und Konfiguration auch die Paketverwaltung. Darauf folgt ein Schnelleinstieg in die Programmierung mit Python und C. Dass diese rund 40 Seiten Programmierkurs einem Einsteiger viel nützen, bezweifle ich. Dennoch ist es ein schöner Überblick, um mit Hilfe von Zusatzliteratur oder Vorkenntnissen schnell zu einem Raspberry-Pi-Programmierer zu werden.

Eine Stärke des Buches sind die Themen, bei denen es um die Erweiterungsfähigkeiten des Raspberry Pis geht. Sowohl das Zusammenspiel mit Arduino als auch die GPIO-Pins und Erweiterungsplatinen wie Gertboard und PiFace werden behandelt. Schade ist, dass dem Leser hier wenig Entscheidungshilfe gegeben wird, welcher Weg sich für was eignet. Drum herum werden noch elektrotechnische Grundlagen vermittelt und das so anschaulich und gut, so dass man sich davon mehr wünschen würde – gerade wenn man eher von der Code-Seite kommt.

Wer einen ansprechenden Einstieg in die Erweiterungsmöglichkeiten des Raspberry Pi sucht, erhält mit diesem Buch einen umfassenden Überblick. Die Breite der behandelten Themen ist groß. Darunter leidet zwar ab und zu der Tiefgang, doch entsprechende Vorkenntnisse oder Zusatzliteratur vorausgesetzt, kann das auch ein Vorteil sein. Das Buch ist durchaus für den ambitionierten Einsteiger geeignet ohne den Fortgeschrittenen zu langweilen.

„Durchstarten mit Raspberry Pi“
Autor: Erik Bartmann
Preis: ca. Euro 25,-
ISBN: 3868994106
Erscheinungsdatum: O’Reilly, 28. Oktober 2012
Link: Amazon