12+ Jahre App-Entwicklung
Schnell mit KI, stark durch Erfahrung
50+ Erfolgreiche App-Projekte

Blog

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.

Artikelbild für Bluetooth 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.

Für Teams, die plattformübergreifende Apps mit Hardware-Anbindung bauen, ist das ein realistisches Beispiel aus der Praxis. Mehr dazu findest du auch auf unseren Seiten zur .NET MAUI App-Entwicklung, Cross-Plattform App Entwicklung und Mobile App Entwicklung.

Wenn du den Beitrag gezielt durchgehen möchten, findest du hier eine schnelle Übersicht:

  1. Vom BeeWi Car zu LEGO Technic
  2. Was das LEGO Wireless Protocol vorgibt
  3. Zielarchitektur in .NET MAUI
  4. Das User Interface
  5. Die gemeinsame Schnittstelle
  6. Die LEGO-spezifischen Konstanten
  7. Ports nach dem Verbindungsaufbau erkennen
  8. Port Output Commands an den Hub senden
  9. Warum der LEGO Porsche spannender ist als das alte BeeWi Car
  10. Wo .NET MAUI in diesem Szenario überzeugt
  11. Wann lohnt sich BLE für IoT-Projekte?
  12. Fazit

Vom BeeWi Car zu LEGO Technic

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.

Mein .NET-MAUI-Layout dafür sieht so aus:

App-Design für die .NET-MAUI-Steuerung des LEGO Porsche GT4

<ContentPage
    x:Class="LegoController.Views.DrivePage"
    xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
    xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
    BackgroundColor="#F5F7FA"
    Shell.NavBarIsVisible="False">

    <ContentPage.Resources>
        <ResourceDictionary>
            <Color x:Key="Ink">#17202A</Color>
            <Color x:Key="Muted">#667085</Color>
            <Color x:Key="Panel">#FFFFFF</Color>
            <Color x:Key="Line">#D9E1EA</Color>
            <Color x:Key="Accent">#E5332A</Color>
            <Color x:Key="AccentDark">#B91F18</Color>
            <Color x:Key="Bluetooth">#246BFE</Color>

            <Style TargetType="Label">
                <Setter Property="TextColor" Value="{StaticResource Ink}" />
                <Setter Property="FontFamily" Value="OpenSansRegular" />
            </Style>

            <Style x:Key="PanelBorder" TargetType="Border">
                <Setter Property="BackgroundColor" Value="{StaticResource Panel}" />
                <Setter Property="Stroke" Value="{StaticResource Line}" />
                <Setter Property="StrokeThickness" Value="1" />
                <Setter Property="StrokeShape" Value="RoundRectangle 8" />
                <Setter Property="Padding" Value="16" />
            </Style>

            <Style x:Key="PrimaryButton" TargetType="Button">
                <Setter Property="BackgroundColor" Value="{StaticResource Accent}" />
                <Setter Property="TextColor" Value="White" />
                <Setter Property="FontAttributes" Value="Bold" />
                <Setter Property="CornerRadius" Value="8" />
                <Setter Property="HeightRequest" Value="48" />
            </Style>

            <Style x:Key="DriveButton" TargetType="Button">
                <Setter Property="BackgroundColor" Value="#17202A" />
                <Setter Property="TextColor" Value="White" />
                <Setter Property="FontAttributes" Value="Bold" />
                <Setter Property="CornerRadius" Value="8" />
                <Setter Property="HeightRequest" Value="58" />
                <Setter Property="FontSize" Value="16" />
            </Style>

            <Style x:Key="SecondaryButton" TargetType="Button">
                <Setter Property="BackgroundColor" Value="#EEF2F6" />
                <Setter Property="TextColor" Value="{StaticResource Ink}" />
                <Setter Property="FontAttributes" Value="Bold" />
                <Setter Property="CornerRadius" Value="8" />
                <Setter Property="HeightRequest" Value="48" />
            </Style>
        </ResourceDictionary>
    </ContentPage.Resources>

    <Grid RowDefinitions="Auto,*"
          Padding="20,18,20,0">

        <Grid RowDefinitions="Auto,Auto"
              RowSpacing="10"
              Padding="0,6,0,16">
            <VerticalStackLayout Spacing="2">
                <Label Text="LEGO Porsche GT4"
                       FontSize="26"
                       FontAttributes="Bold" />
                <Label Text="BLE Control Center"
                       FontSize="14"
                       TextColor="{StaticResource Muted}" />
            </VerticalStackLayout>

            <Grid Grid.Row="1"
                  ColumnDefinitions="102,*,102">
                <Border StrokeShape="RoundRectangle 8"
                        Stroke="#B7C4D6"
                        BackgroundColor="#EAF0F8"
                        Padding="10,7"
                        WidthRequest="102">
                    <Grid ColumnDefinitions="Auto,*"
                          ColumnSpacing="8">
                        <BoxView WidthRequest="8"
                                 HeightRequest="8"
                                 CornerRadius="4"
                                 Color="{Binding ConnectionColor}"
                                 VerticalOptions="Center" />
                        <Label Grid.Column="1"
                               Text="{Binding ConnectionState}"
                               FontSize="12"
                               FontAttributes="Bold"
                               TextColor="{StaticResource Ink}"
                               VerticalOptions="Center" />
                    </Grid>
                </Border>

                <Button Grid.Column="2"
                        Text="Trennen"
                        Command="{Binding DisconnectCommand}"
                        Style="{StaticResource PrimaryButton}"
                        FontSize="12"
                        WidthRequest="102"
                        HeightRequest="34" />
            </Grid>
        </Grid>

        <ScrollView Grid.Row="1"
                    VerticalScrollBarVisibility="Never">
            <VerticalStackLayout Spacing="14"
                                 Padding="0,0,0,24">

                <Grid ColumnDefinitions="*,*"
                      ColumnSpacing="12">
                    <Border Style="{StaticResource PanelBorder}">
                        <VerticalStackLayout Spacing="6">
                            <Label Text="Akku"
                                   FontSize="12"
                                   TextColor="{StaticResource Muted}" />
                            <Label Text="{Binding BatteryLevel, StringFormat='{}{0}%'}"
                                   FontSize="24"
                                   FontAttributes="Bold" />
                            <ProgressBar Progress="{Binding BatteryProgress}"
                                         ProgressColor="{StaticResource Bluetooth}" />
                        </VerticalStackLayout>
                    </Border>

                    <Border Grid.Column="1"
                            Style="{StaticResource PanelBorder}">
                        <VerticalStackLayout Spacing="6">
                            <Label Text="Signal"
                                   FontSize="12"
                                   TextColor="{StaticResource Muted}" />
                            <Label Text="{Binding Rssi, StringFormat='{}{0} dBm'}"
                                   FontSize="24"
                                   FontAttributes="Bold" />
                            <Label Text="{Binding LastNotification}"
                                   FontSize="12"
                                   TextColor="{StaticResource Muted}" />
                        </VerticalStackLayout>
                    </Border>
                </Grid>

                <Border Style="{StaticResource PanelBorder}">
                    <VerticalStackLayout Spacing="16">
                        <Grid ColumnDefinitions="*,Auto">
                            <VerticalStackLayout Spacing="2">
                                <Label Text="Fahrsteuerung"
                                       FontSize="18"
                                       FontAttributes="Bold" />
                                <Label Text="{Binding ActivePortSummary}"
                                       FontSize="12"
                                       TextColor="{StaticResource Muted}" />
                            </VerticalStackLayout>

                            <Button Grid.Column="1"
                                    Text="STOP"
                                    Command="{Binding EmergencyStopCommand}"
                                    BackgroundColor="{StaticResource AccentDark}"
                                    TextColor="White"
                                    FontAttributes="Bold"
                                    CornerRadius="8"
                                    WidthRequest="82"
                                    HeightRequest="44" />
                        </Grid>

                        <Grid RowDefinitions="58,58,58"
                              ColumnDefinitions="*,92,*"
                              RowSpacing="20"
                              ColumnSpacing="16"
                              Margin="0,28,0,6">
                            <Button Grid.Column="1"
                                    Text="Vor"
                                    Pressed="ForwardPressed"
                                    Released="ForwardReleased"
                                    Style="{StaticResource DriveButton}" />

                            <Button Grid.Row="1"
                                    Text="Links"
                                    Pressed="LeftPressed"
                                    Released="LeftReleased"
                                    Style="{StaticResource DriveButton}" />

                            <Border Grid.Row="1"
                                    Grid.Column="1"
                                    BackgroundColor="#F2F4F7"
                                    Stroke="#D9E1EA"
                                    StrokeShape="RoundRectangle 8"
                                    Padding="10">
                                <VerticalStackLayout Spacing="2"
                                                     VerticalOptions="Center">
                                    <Label Text="{Binding DrivePower, StringFormat='{}{0}%'}"
                                           HorizontalTextAlignment="Center"
                                           FontSize="22"
                                           FontAttributes="Bold" />
                                    <Label Text="Leistung"
                                           HorizontalTextAlignment="Center"
                                           FontSize="11"
                                           TextColor="{StaticResource Muted}" />
                                </VerticalStackLayout>
                            </Border>

                            <Button Grid.Row="1"
                                    Grid.Column="2"
                                    Text="Rechts"
                                    Pressed="RightPressed"
                                    Released="RightReleased"
                                    Style="{StaticResource DriveButton}" />

                            <Button Grid.Row="2"
                                    Grid.Column="1"
                                    Text="Zurück"
                                    Pressed="BackwardPressed"
                                    Released="BackwardReleased"
                                    Style="{StaticResource DriveButton}" />
                        </Grid>

                    </VerticalStackLayout>
                </Border>

                <Border Style="{StaticResource PanelBorder}">
                    <Grid ColumnDefinitions="*,Auto"
                          ColumnSpacing="16">
                        <VerticalStackLayout Spacing="4">
                            <Label Text="Licht"
                                   FontSize="18"
                                   FontAttributes="Bold" />
                            <Label Text="{Binding LightStateText}"
                                   FontSize="12"
                                   TextColor="{StaticResource Muted}" />
                        </VerticalStackLayout>

                        <Switch Grid.Column="1"
                                IsToggled="{Binding LightsEnabled}"
                                ThumbColor="White"
                                OnColor="{StaticResource Accent}" />
                    </Grid>
                </Border>

                <Border Style="{StaticResource PanelBorder}"
                        Padding="16,8">
                    <Grid ColumnDefinitions="*,Auto">
                        <Label Text="Erkannte Ports"
                               FontSize="14"
                               FontAttributes="Bold"
                               VerticalOptions="Center" />
                        <Label Grid.Column="1"
                               Text="{Binding ActivePortCountText}"
                               FontSize="12"
                               TextColor="{StaticResource Bluetooth}"
                               VerticalOptions="Center" />
                    </Grid>
                </Border>
            </VerticalStackLayout>
        </ScrollView>
    </Grid>
</ContentPage>

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.

public interface ILegoVehicleService
{
    Task<IReadOnlyList<LegoHubInfo>> ScanAsync(CancellationToken cancellationToken);
    Task ConnectAsync(string deviceId, CancellationToken cancellationToken);
    Task DisconnectAsync(CancellationToken cancellationToken);
    Task InitializeAsync(CancellationToken cancellationToken);
    Task SetDrivePowerAsync(sbyte power, CancellationToken cancellationToken);
    Task SetSteeringPowerAsync(sbyte power, CancellationToken cancellationToken);
    Task StopDriveAsync(CancellationToken cancellationToken);
    Task StopSteeringAsync(CancellationToken cancellationToken);
    Task ToggleLightsAsync(CancellationToken cancellationToken);
}

public record LegoHubInfo(string Id, string Name);

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;
}

Diese UUIDs und Nachrichtentypen stammen direkt aus der offiziellen LEGO-Dokumentation.

Ports nach dem Verbindungsaufbau erkennen

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:

  1. Verbindung zum Hub aufbauen
  2. Notifications auf der LEGO Characteristic abonnieren
  3. Eingehende Nachrichten analysieren
  4. 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:

private async Task SendStartPowerCommandAsync(byte portId, sbyte power, CancellationToken cancellationToken)
{
    byte startupAndCompletion = 0x11; // execute immediately + command feedback

    var payload = new byte[]
    {
        0x06, // Länge
        0x00, // Hub ID
        LegoBleConstants.PortOutputCommandMessageType,
        portId,
        startupAndCompletion,
        0x01, // StartPower
        unchecked((byte)power)
    };

    await WriteAsync(payload, cancellationToken);
}

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.

Jetzt Projekt besprechen

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:

  • BLE statt klassischem Bluetooth-Socket
  • Dokumentiertes Protokoll statt proprietärer Einzelbefehle
  • 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.

Wenn du ähnliche Anwendungen planst, lohnt sich auch ein Blick auf unsere Leistungen rund um .NET MAUI App-Entwicklung, Xamarin-Migration auf .NET MAUI, Cross-Plattform App Entwicklung, Android App Entwicklung und iOS App Entwicklung.

FAQ

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

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.

Verwandte Artikel

Erweitere deine Xamarin.iOS-App mit OpenStreetMap

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.

Automatisierte UI-Tests leicht gemacht mit Xamarin.UITest

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.

Ein BeeWi-Auto mit Xamarin iOS steuern

Lerne in 5 einfachen Schritten, wie Bluetooth und das External Accessory Framework zusammenspielen, um ein BeeWi-Auto mit Xamarin.iOS zu steuern.