Skip to content

.NET implementation of the LEGO PoweredUp Protocol

License

Notifications You must be signed in to change notification settings

KeyDecoder/powered-up

 
 

Repository files navigation

SharpBrick.PoweredUp

SharpBrick.PoweredUp is a .NET implementation of the Bluetooth Low Energy Protocol for Lego Powered UP products.

Nuget license:MIT GitHub issues by-label GitHub issues by-label

Build-CI Build-Release

Features

  • Multiple Programming Models: SharpBrick.PoweredUp supports usage in a device model (hubs and devices as classes/properties; see examples below) or a protocol level (messages send up and down the Bluetooth Low Energy Protocol).
  • Typed Devices with explicit Functions: The SDK supports most commands described in the Lego Wireless Protocol in its typed devices (Motors, Lights, ..). They are self-describing to support a quick bootup of the SDK.
  • Dynamic Devices: The SDK can auto-discover new devices which are not yet known by the SDK. The device can be directly accessed either by writing data directly to a mode or receiving notification about value changes.
  • Awaitable Commands: Instead of waiting a defined amount of time for the devices to react, directly listens to the feedback messages the LEGO Wireless Protocol provides. No unecessary delays and race conditions.
  • Port Value Combined Mode: If supported by the device, the SDK allows you to configure the devices to combine multiple feedbacks of the same device within the same message (e.g. speed and absolute position of a motor).
  • Virtual Port Creation: Combine multiple devices of the same type into a virtual combined port. This allows synchronous access to multiple devices using the same message (e.g. using two motors for driving).
  • Deployment Model Verification: The SDK includes a model builder and a verification method to ensure that the wired devies are correctly reflecting the expectations in the program.
  • Tools: The poweredup CLI includes a device list feature, enumerating the metadata properties of the LEGO Wireless Protocol.
  • Support for multiple Bluetooth Stacks: Use either Microsoft WinRT, BlueGiga Bluetooth (Silicon's Lab BlueGiga-adapter, for example BLED112) or Xamarin using BLE.Plugin to talk to your Lego hubs.

Examples

Additional to code fragments below, look into the examples/SharpBrick.PoweredUp.Examples project (15+ examples).

using SharpBrick.PoweredUp;

using Microsoft.Extensions.DependencyInjection; // SharpBrick.PoweredUp uses the DI system
using Microsoft.Extensions.Logging; // SharpBrick.PoweredUp also logs stuff

Discovering Hubs

var serviceProvider = new ServiceCollection()
    .AddLogging()
    .AddPoweredUp()
    .AddWinRTBluetooth() // using WinRT Bluetooth on Windows (separate NuGet SharpBrick.PoweredUp.WinRT; others are available)
    .BuildServiceProvider();
    
var host = serviceProvider.GetService<PoweredUpHost>();

var hub = await host.DiscoverAsync<TechnicMediumHub>();
await hub.ConnectAsync();

Discovering Hubs for UI

var host = serviceProvider.GetService<PoweredUpHost>();

var cts = new CancellationTokenSource();
host.Discover(async hub =>
{
    await hub.ConnectAsync(); // to get some more properties from it

    // show in UI
}, cts.Token);

// Cancel Button => cts.Cancel();

Sending Commands to Ports and Devices of a Hub

See source code in examples/SharpBrick.PoweredUp.Examples for more examples.

// do hub discovery before

using (var technicMediumHub = hub as TechnicMediumHub)
{
    // optionally verify if everything is wired up correctly
    await technicMediumHub.VerifyDeploymentModelAsync(modelBuilder => modelBuilder
        .AddHub<TechnicMediumHub>(hubBuilder => hubBuilder
            .AddDevice<TechnicXLargeLinearMotor>(technicMediumHub.A)
        )
    );

    await technicMediumHub.RgbLight.SetRgbColorsAsync(0x00, 0xff, 0x00);

    var motor = technicMediumHub.A.GetDevice<TechnicXLargeLinearMotor>();

    await motor.GotoPositionAsync(45, 10, 100, PortOutputCommandSpecialSpeed.Brake);
    await Task.Delay(2000);
    await motor.GotoPositionAsync(-45, 10, 100, PortOutputCommandSpecialSpeed.Brake);

    await technicMediumHub.SwitchOffAsync();
}

Receiving values from Ports and Devices of a Hub (single value setup)

var motor = technicMediumHub.A.GetDevice<TechnicXLargeLinearMotor>();

await motor.SetupNotificationAsync(motor.ModeIndexAbsolutePosition, true);

// observe using System.Reactive
var disposable = motor.AbsolutePositionObservable.Subscribe(x => Console.WriteLine(x.SI));
// ... once finished observing (do not call immediately afterwards ;))
disposable.Dispose();

// OR manually observe it
Console.WriteLine(motor.AbsolutePosition);

Connecting to an unknown device

// deployment model verification with unknown devices
await technicMediumHub.VerifyDeploymentModelAsync(mb => mb
    .AddAnyHub(hubBuilder => hubBuilder
        .AddAnyDevice(0))
    );

var dynamicDeviceWhichIsAMotor = technicMediumHub.Port(0).GetDevice<DynamicDevice>();

// or also direct from a protocol
//var dynamicDeviceWhichIsAMotor = new DynamicDevice(technicMediumHub.Protocol, technicMediumHub.HubId, 0);

// discover the unknown device using the LWP (since no cached metadata available)
await dynamicDeviceWhichIsAMotor.DiscoverAsync();

// use combined mode values from the device
await dynamicDeviceWhichIsAMotor.TryLockDeviceForCombinedModeNotificationSetupAsync(2, 3);
await dynamicDeviceWhichIsAMotor.SetupNotificationAsync(2, true);
await dynamicDeviceWhichIsAMotor.SetupNotificationAsync(3, true);
await dynamicDeviceWhichIsAMotor.UnlockFromCombinedModeNotificationSetupAsync(true);

// get the individual modes for input and output
var powerMode = dynamicDeviceWhichIsAMotor.SingleValueMode<sbyte>(0);
var posMode = dynamicDeviceWhichIsAMotor.SingleValueMode<int>(2);
var aposMode = dynamicDeviceWhichIsAMotor.SingleValueMode<short>(3);

// use their observables to report values
using var disposable = posMode.Observable.Subscribe(x => Console.WriteLine($"Position: {x.SI} / {x.Pct}"));
using var disposable2 = aposMode.Observable.Subscribe(x => Console.WriteLine($"Absolute Position: {x.SI} / {x.Pct}"));

// or even write to them
await powerMode.WriteDirectModeDataAsync(0x64); // That is StartPower(100%) on a motor
await Task.Delay(2_000);
await powerMode.WriteDirectModeDataAsync(0x00); // That is Stop on a motor

Console.WriteLine($"Or directly read the latest value: {aposMode.SI} / {aposMode.Pct}%");

Connect to Hub and Send a Message and retrieving answers (directly on protocol layer)

Note: The ILegoWirelessProtocol class was renamed in 3.0. Previously it is known as IPoweredUpProtocol.

var serviceProvider = new ServiceCollection()
    .AddLogging()
    .AddPoweredUp()
    .AddWinRTBluetooth() // using WinRT Bluetooth on Windows (separate NuGet SharpBrick.PoweredUp.WinRT)
    .BuildServiceProvider();

using (var scope = serviceProvider.CreateScope()) // create a scoped DI container per intented active connection/protocol. If disposed, disposes all disposable artifacts.
{
    // init BT layer with right bluetooth address
    scope.ServiceProvider.GetService<BluetoothKernel>().BluetoothAddress = bluetoothAddress;

    var protocol = scope.GetService<ILegoWirelessProtocol>();

    await protocol.ConnectAsync(); // also connects underlying BT connection
    
    using disposable = protocol.UpstreamMessages.Subscribe(message =>
    {
        if (message is HubPropertyMessage<string> msg)
        {
            Console.WriteLine($"Hub Property - {msg.Property}: {msg.Payload}");
        }
    });

    await protocol.SendMessageAsync(new HubPropertyMessage() { 
        Property = HubProperty.AdvertisingName, 
        Operation = HubPropertyOperation.RequestUpdate
    });

    Console.Readline(); // allow the messages to be processed and displayed. (alternative: SendMessageReceiveResultAsync, SendPortOutputCommandAsync, ..)

    // fun with light on hub 0 and built-in LED on port 50
    var rgbLight = new RgbLight(protocol, 0, 50);
    await rgbLight.SetRgbColorsAsync(0x00, 0xff, 0x00);

    // fun with motor on hub 0 and port 0
    var motor = new TechnicXLargeLinearMotor(protocol, 0, 0);
    await motor.GotoPositionAsync(45, 10, 100, PortOutputCommandSpecialSpeed.Brake);
    await Task.Delay(2000);
    await motor.GotoPositionAsync(-45, 10, 100, PortOutputCommandSpecialSpeed.Brake);
}

Connecting with other Bluetooth Adapters

  • BlueGiga Adapter: Find more details in the docs about the needed configuration and the setup on Windows, Ubuntu or the Raspberry Pi.

Command Line Experience

Note: Due to upstream issues in the dotnet/sdk (issue) we are currently not providing the NuGet distributed dotnet tool. The CLI itself can be used by dotnet run -- when compiling this project directly.

The poweredup command line utility intends to allow the inspection of LEGO Wireless Protocol / Powered UP hubs and devices for their properties. It has utilities for ...

  • Enumerating all connected Devices including hub internal devices and emit their static self-description as they expose using the LEGO Wireless Protocol.

    poweredup device list
    
  • Binary dumping the self-description helps protocol implementors with a lack of devices to understand and try to implement the devices without having the physical device. Also the output is needed when programming the library to enable a fast bootup of the SDK.

    poweredup device dump-static-port -p 0
    
  • Pretty Print Binary Dumps: Help to convert a binary dump in a nice representation.

  • Use of other Bluetooth LE stack By default the CLI tools assumes the usage of WinRT. If used on another operating system or with another Bluetooth Adapter on Windows, the Bluetooth Adapter needs to be specified. Adapter might need additional configuration, see their details documentation.

    poweredup device list --BluetoothAdapter BlueGigaBLE
    

Installation Instruction

  1. Install the latest .NET on your machine (e.g. .NET 5).
  2. Install the poweredup dotnet utility using the following instruction
    // On Windows
    dotnet tool install --framework net5.0-windows10.0.19041.0 -g SharpBrick.PoweredUp.Cli
    
    // On Linux
    dotnet tool install --framework net5.0 -g SharpBrick.PoweredUp.Cli
    
  3. Start using the tool
    poweredup device list
    

SDK Status, Hardware Support, Contributions, ..

Basic Architecture within the SDK

+---------+
|         |
| Devices | <-+
|         |   |   +-----------------------+     +-------------+     +-----+
+---------+   +-> |                       |     |             |     |     |
                  | ILegoWirelessProtocol | <-> | BLE Adapter | <-> | BLE |
+---------+   +-> |    (w/ Knowlege)      |     |             |     |     |
|         |   |   +-----------------------+     +-------------+     +-----+
|   Hub   | <-+
|         |
+---------+

DI Container Elements

                                              PoweredUpHost +-------+
                                                   +                |
                                                   |                |
+-------------------- Scoped Service Provider ------------------------+
|                                                  |                | |
|                                                  v                +--->IPoweredUp
| LinearMidCalibration +                         HubFactory         | |  BluetoothAdapter
|                      |                                            | |
| TechnicMediumHub +---+-> LegoWirelessProtocol +-> BluetoothKernel + |
|             +                       +                               |
|             |                       |                               |
|             +-----------------------+--------> DeviceFactory        |
|                                                                     |
+---------------------------------------------------------------------+

Implementation Status

  • Bluetooth Adapter
    • .NET Core 3.1 (on Windows 10 using WinRT Bluetooth). Please use version v3.4.0 and consider upgrading to .NET 5
    • .NET 5 (on Windows 10 using WinRT Bluetooth) (⚠ v4.0 or later)
    • .NET 5 (on Windows 10 using BlueGiga Adapter) (⚠ v4.0 or later)
    • .NET 5 (on Linux using BlueGiga Adapter) (tested on Raspberry Pi 3, 4 and Ubuntu 20.04; ⚠ v4.0 or later)
    • UWP (most likely December 2021; UWP currently does not support .NET Standard 2.1 and C# 8.0+)
    • .NET Framework 4.8 (will never be supported; .NET Framework does not and will never support .NET Standard 2.1 and C# 8.0+)
    • Xamarin 5 (on Android using BLE.Plugin) (⚠ v4.0 or later)
    • Blazor/WebAssembly (on Browser using WebBluetooth; currently blocked by less than ideal GATT support on browsers, see 1, 2 and 3 )
  • Hub Model
    • Hubs
      • Ports
      • Properties
      • Alerts
      • Actions
      • Create Virtual Ports
      • Move Hub (88006)
      • Two Port Hub (88009)
      • Two Port Handset (88010)
      • Technic Medium Hub (88012)
      • MarioHub (set 71360)
      • Duplo Train Base (set 10874)
      • .. other hubs depend on availability of hardware / contributions
    • Devices
      • Technic Medium Hub (88012) - Rgb Light
      • Technic Medium Hub (88012) - Current
      • Technic Medium Hub (88012) - Voltage
      • Technic Medium Hub (88012) - Temperature Sensor 1 + 2
      • Technic Medium Hub (88012) - Accelerometer
      • Technic Medium Hub (88012) - Gyro Sensor
      • Technic Medium Hub (88012) - Tilt Sensor
      • Technic Medium Hub (88012) - Gesture Sensor (⚠ Usable but Gesture mapping is pending)
      • Move Hub (88006) - Rgb Light
      • Move Hub (88006) - Current
      • Move Hub (88006) - Voltage
      • Move Hub (88006) - Tilt
      • Move Hub (88006) - Internal Motors (Single and Virtual Port)
      • Two Port Hub (88009) - Rgb Light
      • Two Port Hub (88009) - Current
      • Two Port Hub (88009) - Voltage
      • Mario Hub (set 71360) - Accelerometer (Raw & Gesture) (⚠ Usable but Gesture mapping is a rough draft)
      • Mario Hub (set 71360) - TagSensor (Barcode & RGB)
      • Mario Hub (set 71360) - Pants
      • Mario Hub (set 71360) - Debug
      • Duplo Train Base (set 10874) - Motor
      • Duplo Train Base (set 10874) - Speaker
      • Duplo Train Base (set 10874) - Rgb Light
      • Duplo Train Base (set 10874) - ColorSensor
      • Duplo Train Base (set 10874) - Speedometer
      • Color Distance Sensor (88007) (⚠ v4.0 or later)
      • Medium Linear Motor (88008)
      • Remote Control Button (88010)
      • Remote Control RSSI (88010)
      • Train Motor (88011)
      • Technic Large Motor (88013)
      • Technic XLarge Motor (88014)
      • Technic Medium Angular Motor (Spike)
      • Technic Medium Angular Motor (Grey)
      • Technic Large Angular Motor (Spike)
      • Technic Large Angular Motor (Grey)
      • Technic Color Sensor
      • Technic Distance Sensor
      • .. other devices depend on availability of hardware / contributions
  • Protocol
  • Features
    • Dynamic Device
    • Deployment Verifier
  • Command Line (dotnet tool install -g SharpBrick.PoweredUp.Cli)
    • poweredup device list (discover all connected devices and their port (mode) properties)
    • poweredup device dump-static-port -p <port number> (see adding new devices tutorial)

SDKs in other programming languages

Resources

Contribution

SharpBrick is an organization intended to host volunteers willing to contribute to the SharpBrick.PoweredUp and related projects. Everyone is welcome (private and commercial entities). Please read our Code of Conduct before participating in our project.

The product is licensed under MIT License to allow a easy and wide adoption into prviate and commercial products.

Thanks ...

Thanks to @nathankellenicki, @dlech, @corneliusmunz, @KeyDecoder, @highstreeto, @Berdsen , @vuurbeving and @dkurok for their code, answers, testing and other important contributions.

About

.NET implementation of the LEGO PoweredUp Protocol

Resources

License

Code of conduct

Stars

Watchers

Forks

Packages

No packages published

Languages

  • C# 100.0%