SharpBrick.PoweredUp is a .NET implementation of the Bluetooth Low Energy Protocol for Lego Powered UP products.
poweredup
CLI includes a device list feature, enumerating the metadata properties of the LEGO Wireless Protocol.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
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();
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();
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();
}
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);
// 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}%");
var serviceProvider = new ServiceCollection()
.AddLogging()
.AddPoweredUp()
.AddWinRTBluetooth()
.BuildServiceProvider();
// getting utilities
var bt = serviceProvider.GetService<IPoweredUpBluetoothAdapter>();
var host = serviceProvider.GetService<PoweredUpHost>();
// discover a LWP bluetooth device
var tcs = new TaskCompletionSource<ILegoWirelessProtocol>();
bt.Discover(async deviceInfo =>
{
if (!tcs.Task.IsCompleted)
{
var p = host.CreateProtocol(deviceInfo);
tcs.SetResult(p);
}
});
var protocol = await tcs.Task;
// connect the protocol
await protocol.ConnectAsync();
// send a raw message which should work with ANY motor connected to a hub
var response = await protocol.SendPortOutputCommandAsync(new PortOutputCommandStartPowerMessage(
0, // PORT A
PortOutputCommandStartupInformation.ExecuteImmediately, PortOutputCommandCompletionInformation.CommandFeedback,
100
)
{
HubId = 0, // as if we ever see another one
});
await Task.Delay(2000);
await protocol.DisconnectAsync();
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
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
poweredup device list
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 |
| |
+---------------------------------------------------------------------+
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)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 to @nathankellenicki, @dlech, @corneliusmunz, @KeyDecoder, @highstreeto, @Berdsen , @vuurbeving and @dkurok for their code, answers, testing and other important contributions.