Bluetooth LE mit .NET MAUI entwickeln: LEGO Porsche GT4 als Praxisbeispiel
Bluetooth Low Energy mit .NET MAUI: Dieser Artikel zeigt, wie sich BLE-fähige Geräte plattformübergreifend für Android und iOS ansprechen lassen. Als Praxisbeispiel dient der LEGO Porsche GT4 e-Performance mit dem offiziellen LEGO Wireless Protocol, inklusive Hub-Service, Port-Erkennung und Motorsteuerung.
BlogBluetooth LE mit .NET MAUI entwickeln: LEGO Porsche GT4 als Praxisbeispiel
Bluetooth Low Energy ist heute die Basis für viele Hardware-Integrationen in mobilen Apps: Medizingeräte, Logistik-Scanner, Smart-Building-Komponenten und ferngesteuerte Fahrzeuge. Mit .NET MAUI lässt sich dafür eine gemeinsame App für Android und iOS entwickeln, die native BLE-Kommunikation über eine saubere Abstraktionsschicht kapselt. Wer unsere älteren Beiträge zu Xamarin kennt, erkennt das Grundprinzip sofort wieder: Eine mobile App verbindet sich mit einem Gerät, sendet Steuerbefehle und reagiert auf Ereignisse in Echtzeit. Als konkretes Praxisbeispiel dient hier der LEGO Porsche GT4 e-Performance Race Car (42176), der das offizielle LEGO Wireless Protocol über BLE implementiert.
Das Modell gehört zur LEGO Technic CONTROL+ Reihe und wird als fernsteuerbares Fahrzeug beschrieben, das sich mit der CONTROL+ App steuern lässt, inklusive Lenkung, Vorwärts- und Rückwärtsfahrt, Lichtsteuerung und Live-Datenanzeige. Genau das macht den Porsche GT4 e-Performance zu einem spannenden Kandidaten für ein eigenes Experiment mit .NET MAUI und nativer BLE-Kommunikation.
Im alten Xamarin-Projekt ging es damals darum, ein kleines Fahrzeug per App zu steuern. Heute ist der technische Unterbau anspruchsvoller: Statt RFCOMM und klassischem Bluetooth kommt ein BLE-basiertes Hub-Profil mit definierten Nachrichtenformaten, Port-Ereignissen und Output-Kommandos zum Einsatz. Das UI, das ViewModel und große Teile der App-Logik können gemeinsam entwickelt werden. Das ist genau der Punkt, an dem .NET MAUI seine Stärke ausspielt. Die BLE-Anbindung und die Kommunikation mit dem LEGO Hub bleiben plattformspezifisch oder werden über eine passende Abstraktion gekapselt.
Wenn du eine bestehende Xamarin-App in diese Richtung modernisieren möchtest, findest du auf unserer Seite zur Xamarin-Migration auf .NET MAUI passende Anknüpfungspunkte.
Was das LEGO Wireless Protocol vorgibt
Das offizielle LEGO-Protokoll beschreibt für LEGO Bluetooth 3.x Hubs einen zentralen BLE-Service mit genau einer GATT-Characteristic. Der LEGO Hub Service verwendet die UUID 00001623-1212-EFDE-1623-785FEABCD123 und die zugehörige Characteristic die UUID 00001624-1212-EFDE-1623-785FEABCD123. Über diese Characteristic werden sowohl Kommandos geschrieben als auch Hub-Updates per Notification empfangen.
Für eine eigene App sind vor allem drei Dinge wichtig:
Das Gerät wird per Bluetooth Low Energy gefunden und verbunden.
Nach dem Verbindungsaufbau meldet der Hub angeschlossene Komponenten über Hub Attached I/O Nachrichten vom Typ 0x04.
Motoren und andere Aktoren werden über Port Output Command Nachrichten vom Typ 0x81 angesteuert.
Das bedeutet: Wir sprechen nicht direkt einen „Porsche-Modus” an, sondern den zugrunde liegenden LEGO Hub mit seinen Ports, Motoren und Kommandos.
Zielarchitektur in .NET MAUI
Für die Umsetzung bietet sich folgende Struktur an:
Gemeinsames UI in XAML
Gemeinsames ViewModel
Ein ILegoVehicleService als fachliche Schnittstelle
BLE-Verbindung zum LEGO Hub
Parsing eingehender Hub-Nachrichten
Senden von Port-Kommandos für Antrieb, Lenkung und optional Licht
So bleibt der Code testbar und die eigentliche LEGO-Protokollschicht ist sauber vom UI getrennt. Wie schon geschrieben, eignet sich die .NET MAUI App-Entwicklung für solche Szenarien besonders gut: gemeinsame Oberflächen, gemeinsame Geschäftslogik und dennoch voller Zugriff auf native Plattformfunktionen.
Das User Interface
Wie schon beim BeeWi-Car-Beispiel braucht die App sichtbare Steuerflächen. Für eine echte mobile App sollte das Interface aber mehr leisten als nur einzelne Buttons anzuzeigen: Der Nutzer muss sofort erkennen, ob der Hub verbunden ist, welche Ports erkannt wurden, welchen Leistungs-Sollwert die App gerade sendet und ob kritische Funktionen wie Licht oder Not-Stopp erreichbar sind.
Das Layout zeigt den verbundenen Fahrmodus. Die Hub-Auswahl und der Scan-Vorgang können davor in einem eigenen Zustand oder auf einer separaten Ansicht stattfinden. Für die eigentliche Steuerung bleiben die Pressed- und Released-Events sinnvoll, weil Motorbefehle dadurch nur so lange aktiv sind, wie der Nutzer eine Richtung tatsächlich gedrückt hält. Wichtig ist die Unterscheidung zwischen echten Hub-Daten und App-Zustand:
Akku und Signal sind echte Hub-Werte. Sie lassen sich über Hub Properties abfragen oder abonnieren: Battery Voltage liefert einen Prozentwert, RSSI einen Signalwert in dBm.
Erkannte Ports sind aus Hub Attached I/O Nachrichten abgeleitet. Die App muss daraus selbst Rollen wie Antrieb, Lenkung oder Licht zuordnen.
Leistung ist in diesem UI kein gemessener Live-Motorwert, sondern der zuletzt gesendete Sollwert für den Motorbefehl. Wer echte Motor-Rückmeldungen anzeigen möchte, muss passende Port-Input-Modes des angeschlossenen Motors abonnieren, zum Beispiel Position, Speed oder Tacho, sofern der jeweilige Motor diese Werte unterstützt.
Licht eingeschaltet ist zunächst App-Zustand. Verlässlicher wird die Anzeige, wenn die App zusätzlich Port Output Command Feedback auswertet und den Zustand erst nach erfolgreichem Kommando aktualisiert.
Verbunden, Trennen, ConnectionColor und vor 1 s aktualisiert kommen aus der BLE-Verbindung beziehungsweise aus dem ViewModel, nicht direkt aus einem fertigen LEGO-UI-Wert.
Ebenfalls einen spannenden Ansatz bietet Avalonia. Einen direkten Vergleich dazu findest du hier
Die gemeinsame Schnittstelle
Damit das ViewModel nicht wissen muss, ob es gerade auf Android oder iOS läuft, kapseln wir die Fahrzeuglogik hinter einer Schnittstelle.
Auf dieser Basis kann das ViewModel später einfach Befehle senden, ohne Details der plattformspezifischen BLE-Implementierung kennen zu müssen. Im Unterschied zum alten BeeWi-Beispiel sprechen wir hier nicht nur rohe „Fahrbefehle” an, sondern bereits fachliche Funktionen wie Antrieb, Lenkung und Licht.
Die LEGO-spezifischen Konstanten
Im Protokoll müssen wir den Hub-Service und die Characteristic eindeutig adressieren.
public static class LegoBleConstants
{
public static readonly Guid HubService = Guid.Parse("00001623-1212-EFDE-1623-785FEABCD123");
public static readonly Guid HubCharacteristic = Guid.Parse("00001624-1212-EFDE-1623-785FEABCD123");
public const byte HubPropertiesMessageType = 0x01;
public const byte HubAttachedIoMessageType = 0x04;
public const byte PortOutputCommandMessageType = 0x81;
public const byte PortOutputCommandFeedbackMessageType = 0x82;
public const byte RssiProperty = 0x05;
public const byte BatteryVoltageProperty = 0x06;
public const byte RequestUpdateOperation = 0x05;
public const byte UpdateOperation = 0x06;
}
Ein wichtiger Unterschied zu einfachen Bluetooth-Spielzeugen: Beim LEGO Hub müssen wir zunächst verstehen, welche Komponenten an welchen Ports hängen. Laut Protokoll meldet der Hub angeschlossene I/O-Geräte über Hub Attached I/O Nachrichten des Typs 0x04. Darüber erfährt die App, welche Port-IDs für Motoren oder andere Komponenten relevant sind.
Das bedeutet in der Praxis:
Verbindung zum Hub aufbauen
Notifications auf der LEGO Characteristic abonnieren
Eingehende Nachrichten analysieren
Port-IDs für Antrieb, Lenkung und Licht ermitteln
Ein stark vereinfachtes Parsing könnte so aussehen:
private readonly Dictionary<string, byte> _ports = new();
private void HandleNotification(byte[] data)
{
if (data.Length < 3)
return;
var messageType = data[2];
if (messageType == LegoBleConstants.HubAttachedIoMessageType)
{
var portId = data[3];
var eventType = data[4];
if (eventType == 0x01 && data.Length >= 8) // Attached I/O
{
var ioTypeId = BitConverter.ToUInt16(data, 5);
RegisterPort(portId, ioTypeId);
}
}
}
private void RegisterPort(byte portId, ushort ioTypeId)
{
// Vereinfachte Zuordnung, echte Portrollen müssen praktisch verifiziert werden.
if (!_ports.ContainsKey("drive"))
_ports["drive"] = portId;
else if (!_ports.ContainsKey("steering"))
_ports["steering"] = portId;
else if (!_ports.ContainsKey("lights"))
_ports["lights"] = portId;
}
Entscheidend ist nicht die Heuristik, sondern das Prinzip: Erst wenn die Ports bekannt sind, kann die App gezielt Kommandos an den richtigen Motor oder Ausgang senden.
Port Output Commands an den Hub senden
Im LEGO-Protokoll ist für Motoren der Nachrichtentyp Port Output Command 0x81 vorgesehen. Die Dokumentation beschreibt dafür ein gemeinsames Nachrichtenformat mit Port-ID, Startup-/Completion-Informationen und einem Sub-Command. Außerdem sind dort unter anderem Motor-Kommandos wie StartPower dokumentiert, bei denen Werte von -100 bis 100 die Richtung und Leistung definieren, während 0 für Float und 127 für Brake steht. Für ein einfaches Fahrbeispiel kann man sich eine kleine Hilfsmethode bauen:
Damit lassen sich einfache Steuerfunktionen formulieren:
public Task SetDrivePowerAsync(sbyte power, CancellationToken cancellationToken)
=> SendStartPowerCommandAsync(_ports["drive"], power, cancellationToken);
public Task SetSteeringPowerAsync(sbyte power, CancellationToken cancellationToken)
=> SendStartPowerCommandAsync(_ports["steering"], power, cancellationToken);
public Task StopDriveAsync(CancellationToken cancellationToken)
=> SendStartPowerCommandAsync(_ports["drive"], 0, cancellationToken);
public Task StopSteeringAsync(CancellationToken cancellationToken)
=> SendStartPowerCommandAsync(_ports["steering"], 0, cancellationToken);
Für Lenkung und Licht kann je nach angeschlossenem Gerät und Portmodus eine andere Initialisierung oder ein anderer Sub-Command sinnvoll sein. Das lässt sich nur am realen Modell praktisch validieren.
Sie planen ein Projekt mit Bluetooth LE oder .NET MAUI?
Unser Team hat Erfahrung mit BLE-Integrationen für Android und iOS. Wir helfen Ihnen dabei, die richtige Architektur zu wählen und die Umsetzung auf beiden Plattformen effizient umzusetzen.
Warum der LEGO Porsche spannender ist als das alte BeeWi Car
Das BeeWi Car war ein schönes Einstiegsprojekt. Der LEGO Porsche GT4 e-Performance ist dagegen ein wesentlich realistischeres Beispiel für moderne App-Hardware-Kommunikation:
Mehrere Ports und Geräteklassen statt eines einzigen Zielgeräts
Erweiterbarkeit für Telemetrie, Licht und weitere Funktionen
Deshalb eignet sich das Modell gerade gut, um aus einem alten Xamarin-Demo-Szenario ein zeitgemäßes .NET-MAUI-Beispiel zu machen.
Wo .NET MAUI in diesem Szenario überzeugt
Der eigentliche Mehrwert von .NET MAUI liegt hier nicht darin, BLE „magisch” plattformunabhängig zu machen. Der Mehrwert liegt darin, dass wir gemeinsame Oberflächen, gemeinsame Geschäftslogik und gemeinsame Protokollabstraktionen aufbauen können, ohne die nativen Besonderheiten von Android und iOS zu ignorieren.
Das ist besonders relevant, wenn aus einem Experiment später ein echtes Produkt wird. Genau dann greifen Cross-Plattform-Ansatz, native Plattformintegration und saubere Softwarearchitektur ineinander. Wer solche Integrationen gezielt für Android oder iOS umsetzen möchte, findet auf unseren Seiten zur Android App Entwicklung und iOS App Entwicklung weitere Informationen.
Wann lohnt sich BLE für IoT-Projekte?
Das LEGO-Modell ist ein anschauliches Beispiel, aber Bluetooth Low Energy spielt in deutlich anspruchsvolleren Szenarien eine zentrale Rolle. Überall dort, wo Geräte Energie sparen müssen, keine permanente WLAN-Infrastruktur vorhanden ist und die Kommunikation auf kurze Distanzen beschränkt bleibt, ist BLE die richtige Wahl.
Typische Branchen und Anwendungsfälle:
Medizintechnik: Blutzuckermessgeräte, Herzmonitore und Infusionspumpen übertragen Messdaten per BLE an mobile Apps
Logistik und Retail: Bluetooth-Scanner, Etikettendrucker und Beacon-basierte Ortungssysteme
Smart Building: Schlösser, Sensoren und Steuermodule ohne Dauerstromversorgung
Industrie und Wartung: Diagnosegeräte, die ein Techniker vor Ort per App ausliest oder konfiguriert
Konsumgüter und Wearables: Fitnessgeräte, Kopfhörer, Smart-Home-Zubehör
Gegenüber WLAN-basierten Lösungen punktet BLE vor allem beim Energieverbrauch und bei der einfachen Kopplung ohne zentrale Infrastruktur. Gegenüber proprietären Funklösungen bietet es ein offenes, dokumentiertes Protokoll und breite Plattformunterstützung auf Android und iOS. MQTT als Ergänzung ist sinnvoll, sobald die Kommunikation über das lokale Gerätepaar hinausgeht und ein zentrales Backend eingebunden werden soll.
Für .NET MAUI spricht in diesen Szenarien dasselbe Argument wie beim LEGO-Beispiel: ein gemeinsames Projekt für Android und iOS, eine gemeinsame Protokollschicht und saubere Abstraktionen, die das spätere Testen und Erweitern erleichtern. Das gilt unabhängig davon, ob es um ein Spielzeugauto oder ein Industriegerät geht.
Wer ein solches Projekt plant oder eine bestehende Hardware-Lösung mit einer mobilen App erweitern möchte, findet auf unserer Seite zur .NET MAUI App-Entwicklung und zur Mobile App Entwicklung weitere Informationen.
Fazit
Das alte Xamarin-Projekt rund um das BeeWi Car war ein guter Einstieg in mobile Hardware-Steuerung. Der LEGO Porsche GT4 e-Performance Race Car hebt dieses Prinzip auf ein moderneres Niveau. Statt klassischem Bluetooth-Socketing arbeiten wir mit Bluetooth Low Energy, dem offiziellen LEGO Hub Service und klar definierten Port Output Commands.
Für .NET MAUI ist das ein sehr gutes Szenario: Das UI und große Teile der Logik bleiben plattformübergreifend, während die technische Kommunikation mit dem LEGO Hub sauber gekapselt wird. Wer bestehende mobile Projekte modernisieren oder neue Lösungen mit Hardware-Anbindung bauen möchte, findet in dieser Kombination aus .NET MAUI und LEGO BLE Protocol ein praxisnahes Beispiel.
Das Modell 42176 ist ein fernsteuerbares LEGO Technic Fahrzeug aus der CONTROL+ Reihe. LEGO beschreibt es als interaktives Fahrzeug mit App-Steuerung für Lenkung, Fahrtrichtung, Licht und Live-Daten.
Für die Kommunikation mit dem LEGO Hub kommt Bluetooth Low Energy zum Einsatz. Die offizielle LEGO-Dokumentation beschreibt dafür einen eigenen Hub-Service und eine einzige Characteristic für Schreiben und Notifications.
Der LEGO Hub Service verwendet 00001623-1212-EFDE-1623-785FEABCD123, die zugehörige Characteristic 00001624-1212-EFDE-1623-785FEABCD123. Darüber läuft die Kommunikation mit dem Hub.
Nach dem Verbindungsaufbau sendet der Hub Hub Attached I/O Nachrichten vom Typ 0x04. Darüber kann die App erkennen, welche I/O-Geräte an welchen Ports verfügbar sind.
Motoren werden über Port Output Command Nachrichten vom Typ 0x81 gesteuert. Das LEGO-Protokoll beschreibt dafür unter anderem StartPower-Kommandos mit Leistungswerten zwischen -100 und 100.
Nicht direkt. Die fachliche Idee des alten Projekts bleibt erhalten, technisch ist der Unterbau aber ein anderer. Statt klassischem Bluetooth-Socketing braucht ihr heute eine BLE-basierte Anbindung und die Umsetzung des LEGO Wireless Protocol. Für Bestandslösungen ist das eher eine Modernisierung als eine 1:1-Portierung.
Ja, besonders dann, wenn UI und Geschäftslogik auf mehreren Plattformen geteilt werden sollen, die Geräteanbindung aber dennoch nativ oder über eine eigene Abstraktionsschicht umgesetzt werden muss. Genau dafür ist .NET MAUI in Verbindung mit einer sauberen Architektur gut geeignet.
BLE kommt überall dort zum Einsatz, wo Geräte energieeffizient und ohne WLAN-Infrastruktur kommunizieren sollen. Typische Branchen sind Medizintechnik, Logistik, Retail, Smart Building und industrielle Wartungsanwendungen. Die Kombination aus .NET MAUI und BLE ist besonders dann interessant, wenn dieselbe App auf Android und iOS laufen soll.
BLE eignet sich für kurze Distanzen, energiebeschränkte Geräte und direkte Gerät-zu-Gerät-Kommunikation ohne zentrale Infrastruktur. WLAN und MQTT sind sinnvoller, sobald viele Geräte zentral verwaltet werden sollen oder die Kommunikation über das lokale Gerätepaar hinausgeht. In der Praxis werden BLE und MQTT auch kombiniert eingesetzt.
Der Aufwand hängt stark vom verwendeten Gerät und Protokoll ab. Standardisierte Protokolle wie das LEGO Wireless Protocol oder Bluetooth GATT-Profile reduzieren den Entwicklungsaufwand erheblich, weil Nachrichtenformate und Ports dokumentiert sind. Proprietäre Protokolle erfordern dagegen mehr Analyse- und Integrationsaufwand. Eine saubere Serviceschicht in .NET MAUI amortisiert sich besonders dann, wenn die App auf mehreren Plattformen betrieben werden soll.
Sebastian Seidel
Als Mobile-Enthusiast und Geschäftsführer der Cayas Software GmbH ist es mir ein großes Anliegen, mein Team und unsere Kunden zu unterstützen, neue potenziale zu entdecken und gemeinsam zu wachsen. Hier schreibe ich vor allem zur Entwicklung von Android und iOS-Apps mit Xamarin und .NET MAUI.
Kartenfunktionen sind im mobilen Bereich ein wichtiges Feature. Kaum eine App gewinnt nicht an Wert, wenn sie Karten darstellen kann. Apple und Google machen mit ihren Map-SDKs vieles richtig, aber manchmal stößt man an Grenzen - sei es aus rechtlichen Gründen oder wegen eines Bugs. In meinem Fall war Letzteres der Auslöser. Ich brauchte außerdem eine Alternative, die auch offline funktioniert.
Als Entwickler kennen wir alle das Gerücht, dass jede Art von Test teuer ist. In diesem Beitrag möchte ich einen besseren Weg zeigen, UI-Tests zu schreiben, der sogar bei großen mobilen Apps Spaß machen kann.