LegoDimensions 1.7.0

This package has a SemVer 2.0.0 package version: 1.7.0+1.
dotnet add package LegoDimensions --version 1.7.0                
NuGet\Install-Package LegoDimensions -Version 1.7.0                
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="LegoDimensions" Version="1.7.0" />                
For projects that support PackageReference, copy this XML node into the project file to reference the package.
paket add LegoDimensions --version 1.7.0                
#r "nuget: LegoDimensions, 1.7.0"                
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
// Install LegoDimensions as a Cake Addin
#addin nuget:?package=LegoDimensions&version=1.7.0

// Install LegoDimensions as a Cake Tool
#tool nuget:?package=LegoDimensions&version=1.7.0                

Lego Dimensions portal and tag .NET implementation

The aim of this work is to be able to use Lego Dimensions portal and tags outside of their original purpose. Idea is to automate the usage or the portal led, but as well be able to read tags, with or without the portal to automate other external elements from a simple Raspberry PI for example.

You will find a detailed documentation of the Lego Dimensions protocol here. I built this documentation as I did not find anything that detailed.

Lego Dimensions portal

The Lego Dimensions portals compatible with this implementation are all except the XBox ones.

In this document and in the code, the portal refers to the Lego Dimensions where you place the vehicles and characters.

The Lego Tag or just tag refers to the the vehicle or character that you building when playing the game. They are under the hook NFC cards.

Driver installation

In order to use the portal, you need to setup USB drivers.

For Windows users (7 and more)

  • Plug your Lego Dimensions portal.
  • Download Zadig and run it
  • Select "LEGO Reader V2.10" in the devices list (if necessary, use the Options>List All Devices menu)
  • Select WinUSB (libusb will also work) in the list at right of the green arrow and press the install button.

For Linux users

  • Install libusb and libusb1 if not already installed like sudo apt-get libusb-dev on a Rapsberry Pi
  • Copy the 99-lego-dimensions.rules file into /etc/udev/rules.d/. This will allow you to run the code from any user.
  • Reboot then device or apply the changes

Portal instantiation and basic usage

The code supports as many portal you you want. Each portal will instantiate a unique class. 2 static helpers are available to get an array of portal:

using LegoDimensions;

var portal = LegoPortal.GetFirstPortal();
portal.LegoTagEvent += PortalLegoTagEvent;

Console.WriteLine("Press any key to exit");
while (!Console.KeyAvailable)
{
    Thread.Sleep(1000);
}

portal.Dispose();
Console.WriteLine("End of test");

In the previous example, you can register to Tag events. Those are related to adding and removing Lego tag from the portal.

As an example, you can add this callback function:

void PortalLegoTagEvent(object? sender, LegoTagEventArgs e)
{
    Console.WriteLine($"Tag is present: {e.Present} - UID: {BitConverter.ToString(e.CardUid)}");
    if (e.Present)
    {
        Console.WriteLine($"Tag is a {e.LegoTag?.GetType().Name} - Name= {e.LegoTag?.Name}");
    }
}

While placing and removing tags on the different pads, you'll get output like this:

Tag is present: True - UID: 04-CA-8A-B2-6D-40-80
Tag is a Vehicle - Name= Scooby Snack
Tag is present: True - UID: 04-64-74-FA-00-49-81
Tag is a Character - Name= Ethan Hunt
Tag is present: False - UID: 04-64-74-FA-00-49-81

The event object contains more information like the index. The index and the pad are need once you want to do some specific operations.

Lego Dimensions pad color adjustment

You can adjust the leds present in the portal. Most functions will allow you to adjust one or all pads at the same time.

Set color immediately on a pad

The SetColor function allows you to set the color on one or all pads at the same time. You will find example bellow.

Console.WriteLine("Pad left blue");
portal.SetColor(Pad.Left, Color.Blue);
Thread.Sleep(1000);
Console.WriteLine("Pad center white");
portal.SetColor(Pad.Center, Color.White);
Thread.Sleep(1000);
Console.WriteLine("Pad right purple");
portal.SetColor(Pad.Right, Color.Red);
Thread.Sleep(1000);
Console.WriteLine("All yellow");
portal.SetColor(Pad.AllPads, Color.Yellow);

Get color displayed on a specific pad

You can as well check the color that is currently displayed on a specific pad.

Console.WriteLine("All yellow");
portal.SetColor(Pad.AllPads, Color.Yellow);
Thread.Sleep(200);
var col = portal.GetColor(Pad.Left);
Console.WriteLine($"Pad left color: {col.R}-{col.G}-{col.B}");

If for some reason, this function does not get a proper answer, the Black color is the one which will be returned.

Set colors immediately on all pads

This SetColorAll function will set a specific color on all pads. Each pad can had a different color. The difference with te SetColor function is that on the SetColor one, if you are using all pads, they will have the same color.

Console.WriteLine("Pad left Blue, center white, right red");
portal.SetColorAll(Color.White, Color.Blue, Color.Red);

Flashing a pad

You can flash a pad or all at the same time with the same settings using the Flash function.

The FlashPad uses 3 settings:

  • The tick on setting will count how much time it will stay on. The smallest, the fastest.
  • The tick off setting will count how much time it will stay off. The smallest, the fastest.
  • The tick count represent the number of change operation. So a full blink is actually 2 counts. So odd number will always finish on the previous color while the even ones on the new color.
Console.WriteLine("Pad right azure flashing 20 times (10 on and 10 off)");
portal.Flash(Pad.Right, new FlashPad(20, 20, 20, Color.Azure));

Flashing all pads

This FlashAll is similar to the Flash one except you can individually set a specific configuration for each pad. Settings are the same.

You have one additional setting: Enabled which allow to have the pad switched off or on.

Console.WriteLine("Pad center red asymmetric flashing 40 times (20 on and 20 off)");
Console.WriteLine("Pad left green flashing forever times fast (10 on and 10 off)");
Console.WriteLine("Pad right azure flashing 20 times (10 on and 10 off)");
portal.FlashAll(new FlashPad(10, 30, 40, Color.Red), new FlashPad(1, 1, 255, Color.Green), new FlashPad(20, 20, 20, Color.Azure));

Fading a pad

You can fade one or all pads at the same time. The fading speed is determined by the first parameter in the FadePad class and the count by the second. A count is a pulse, so either fading down either up, so you'll need 2 to have a full down then up. For the speed, the high the number is, the slowest is the pulse.

Console.WriteLine("Fade center pad relatively slowly from the displayed color to red, will finish on the new color as odd number of count.");
portal.Fade(Pad.Center, new FadePad(50, 5, Color.Red));

Fading all pads

As for the Fade, you can use FadeAll to fade all pads at the same time. Each pad can have its own setting. Settings are the same. You have an additional one Enabled which allow to have the pad activated or not.

Console.WriteLine("Fade center pad relatively slowly from the displayed color to red, will finish on the new color as odd number of count.");
Console.WriteLine("Fade left pad relatively quit fast from the displayed color to green, will finish on the old color as even number of count.");
Console.WriteLine("Fade right will not fade as disabled regardless of the values placed in the Fade Pad.");
portal.FadeAll(new FadePad(50, 5, Color.Red), new FadePad(5, 50, Color.Green), new FadePad(10, 100, Color.Yellow, false));

Fading a pad to a random color

This can be used to fade a pad on a random color, the speed and the count are the same settings as for the other fade functions.

Console.WriteLine("Randome fading on left pad");
portal.FadeRandom(Pad.Left, 10, 10);
Thread.Sleep(1000);
Console.WriteLine("Randome fading on center pad");
portal.FadeRandom(Pad.Center, 1, 100);
Thread.Sleep(1000);
Console.WriteLine("Randome fading on right pad");
portal.FadeRandom(Pad.Right, 5, 15);
Thread.Sleep(1000);

Reading tags

You can read tags when present on the pad. The following example shows how you can dump a full tag:

Console.WriteLine("Place at least 1 tag on the portal, this will dump the full tag.");
while (!portal.PresentTags.Any())
{
    Thread.Sleep(1000);
}

var idx = portal.PresentTags.First().Index;
for (byte i = 0; i < 0x2c; i += 4)
{
    var tag = portal.ReadTag(idx, i);
    if (tag.Length == 0)
    {
        Console.WriteLine($"Error reading card page 0x{i:X2}")
    }
    else
    {
        Console.WriteLine($"Tag: {BitConverter.ToString(tag)}");
    }
}

If there will be an error reading the card, you'll get an empty buffer. This can happen if you are moving the card of if you're using a non initialized card. See the section on the Lego Tags to understand how to initialize a card.

Write a tag

To be documented later.

List tags on the pad

You should keep track of the tags with their UID. But a list of available tab with their index and associated tag is available through the PresentTags property as well as when calling the ListTags function.

Lego Tags

The Lego Dimensions library contains the full support to handle the Lego Tags. A full solution detecting the tags, reading them and displaying the characters and vehicles is available in the Lego Dimensions Read NFC project.

The Read NFC project is currently using a PN532 connected in serial to read the tags. It's based on the .NET IoT implementation for both the reader and the Ultralight tags.

The example is complete and gives all the steps to:

  • Connect the PN532
  • Get a card, process the ultralight card
  • Generate the authentication key (4 bytes) to read the protected data tags
  • Read the pages 0x24, 0x25, 0x26 and 0x27 where the actual tag elements are stored
  • Process and decrypt the vehicle or character information
  • Display the vehicle or character information
  • Do this over and over, detect new cards, handle issues with non supported cards or potential errors

You just need to adjust the serial port used (auto detection can be an improvement). Mind the name of the port that can be different depending on which operating system you'll run it. This code will work on Windows, Linux and MacOS wherever .NET 6.0 and further is supported.

Unit Tests are also provided to make sure any change will not break the encoding/decoding.

Reading card data on an external NFC reader like a PN532

The ultralight NFC card is protected with password. This password is a 4 bytes. It can be processed with the LegoTag class with the PasswordGenerator function:

Debug.WriteLine("Generating authentication key");
ultralight.AuthenticationKey = LegoTag.GenerateCardPassword(ultralight.SerialNumber);
Debug.WriteLine($"Authentication key: {BitConverter.ToString(ultralight.AuthenticationKey)}");
ultralight.Command = UltralightCommand.PasswordAuthentication;
var auth = ultralight.RunUltralightCommand();

// read page 0x24 to 0x27
ultralight.BlockNumber = 0x24;
ultralight.Command = UltralightCommand.Read16Bytes;
var res = ultralight.RunUltralightCommand();
// Check we do have a result
if (res > 0)
{
    // Process the data
}

Processing data

To process the data, you'll need a valid read from the previous section. It's a 16 byte buffer.

// If page 0x26 == 00 01 00 00 we have a vehicle
if (LegoTag.IsVehicle(ultralight.Data.AsSpan(8, 4).ToArray()))
{
    Console.WriteLine("Found a vehicle.");
    // The 2 first one used
    var id = LegoTag.GetVehicleId(ultralight.Data);
    Console.Write($"vehicle ID: {id} ");
    Vehicle vec = Vehicle.Vehicles.FirstOrDefault(m => m.Id == id);
    if (vec is not null)
    {
        Console.WriteLine($"{vec.Name} - {vec.World}");
    }
    else
    {
        Console.WriteLine("and vehicle does not exist!");
    }
}
else
{
    Console.WriteLine("Found a character.");
    var id = LegoTag.GetCharacterId(ultralight.SerialNumber, ultralight.Data.AsSpan(0, 8).ToArray());
    Console.Write($"Character ID: {id} ");
    Character car = Character.Characters.FirstOrDefault(m => m.Id == id);
    if (car is not null)
    {
        Console.WriteLine($"{car.Name} - {car.World}");
    }
    else
    {
        Console.WriteLine("and character does not exist!");
    }
}

As a result, by placing different tags, you'll get something like:

Place the tag on the reader!
Found a vehicle.
vehicle ID: 123 Ghost Trap - Ghostbusters
Found a character.
Character ID: 42 Wicked Witch - Wizard of Oz
Found a character.
Character ID: 2 Gandalf - Lord of the Rings
Found a vehicle.
vehicle ID: 11 The Annihilator - The Lego Movie

Writing data on a new NTAG213

The regular Lego Tag are protected and except the vehicle tag that can be written at any time, you can't change the character ID of a normal Lego tag. But you can write all this on a brand new tag.

If your tag is fully new, then you can just write whatever you want. You have the code in the LegoDimensionsReadNfc application. This application is available in the Releases both for Windows and arm 32 (so you can run it on a Raspberry Pi).

This application is a wizard, so just follow the steps! You can erase a card, write a card, read a card and fully dump a card. This application is a console application but has a nice UX and UI!

This application requires a PN532 to work.

If you want to write your own code to writ tags

In short, the important steps are the following:

  1. Detect the tag

  2. Create a new password based on the Card Unique ID (the 7 bytes) using the LegoTag.GenerateCardPassword

  3. Write it to the 0x2B block on the card (assuming an NTAG213, address is different for 215 and 216)

  4. If you want a vehicle:

    1. Use LegoTag.EncryptVehicleId to get the 4 bytes
    2. Write the 4 bytes on block 0x24
    3. Write { 0x00, 0x01, 0x00, 0x00 } on block 0x26
  5. If you want a character:

    1. Use LegoTag.EncrypCharactertId to get the 8 bytes
    2. Write the first 4 one on block 0x24
    3. Write the next 4 one on block 0x25
    4. If you've been using a vehicle before, write { 0x00, 0x00, 0x00, 0x00 } on block 0x26

And now, you're all set! No need to adjust any other password!

Sources of inspiration and code

Product Compatible and additional computed target framework versions.
.NET net6.0 is compatible.  net6.0-android was computed.  net6.0-ios was computed.  net6.0-maccatalyst was computed.  net6.0-macos was computed.  net6.0-tvos was computed.  net6.0-windows was computed.  net7.0 was computed.  net7.0-android was computed.  net7.0-ios was computed.  net7.0-maccatalyst was computed.  net7.0-macos was computed.  net7.0-tvos was computed.  net7.0-windows was computed.  net8.0 was computed.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last updated
1.7.0 240 8/22/2023
1.6.0 80 7/12/2023
1.5.0 99 4/14/2023
1.4.0 89 4/11/2023
1.3.0 87 4/11/2023
1.2.0 78 4/7/2023
1.1.0 84 4/7/2023
1.0.4 197 4/6/2023
1.0.3 200 4/6/2023
1.0.2 193 4/5/2023
1.0.1 201 4/5/2023
1.0.0 196 4/5/2023
0.1.0 89 4/6/2023