Skip to content

.NET implementation of the LEGO PoweredUp Protocol

License

Notifications You must be signed in to change notification settings

rickjansen-dev/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.

Examples

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

using SharpBrick.PoweredUp;
using SharpBrick.PoweredUp.WinRT; // for WinRT Bluetooth NuGet

Discovering Hubs

var serviceProvider = new ServiceCollection()
    .AddLogging()
    .AddPoweredUp()
    .AddWinRTBluetooth() // using WinRT Bluetooth on Windows
    .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 (v2.0 onwards)
    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
    .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);
}

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)
      • Library uses Span<T> / C# 8.0 and is therefore not supported in .NET Framework 1.0 - 4.8 and UWP Apps until arrival of .NET 5 (WinForms and WPF work in .NET Core 3.1)
      • Library uses WinRT for communication therefore only Windows 10
    • Xamarin (on iOS / Android using ?)
    • Blazor (on Browser using WebBluetooth)
  • Hub Model
    • Hubs
      • Ports
      • Properties
      • Alerts
      • Actions
      • Create Virtual Ports
      • Two Port Hub (88009)
      • Technic Medium Hub (88012)
      • .. 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)
      • Hub (88009) - Rgb Light
      • Hub (88009) - Current
      • Hub (88009) - Voltage
      • Medium Linear Motor (88008)
      • Train Motor (88011)
      • Technic Large Motor (88013)
      • Technic XLarge Motor (88014)
      • Technic Angular Motor (depend on availability of hardware / contributions)
      • .. 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, @corneliusmunz and @vuurbeving 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%