Unleashing .NET on Embedded Linux

A Comprehensive Guide to Running Avalonia Apps on Raspberry Pi with Raspberry Pi OS Lite

...
Jumar Macato

This guide walks through the process of running .NET applications using Avalonia on embedded Linux devices, focusing on the Raspberry Pi with Raspberry Pi OS Lite. We'll cover everything from initial Raspberry Pi setup to developing and deploying an Avalonia application that uses Direct Rendering Manager (DRM) for graphics rendering.

Avalonia has gained significant traction in the embedded systems world across various industries. Manufacturing, medical, and scientific sectors have increasingly adopted Avalonia for their embedded applications. A notable example is Schneider Electric, a global leader in energy management and automation. After evaluating all available options in the ecosystem, Schneider Electric chose Avalonia as part of their strategy to transition their Harmony range of HMIs to Linux.

This shift towards Linux-based solutions using frameworks like Avalonia isn't just a trend - it's a practical move driven by the need for more flexible, cost-effective, and performant embedded systems. Throughout this guide, we'll explore the technical aspects that make this approach attractive to companies like Schneider Electric and how you can implement similar solutions in your projects.

We'll start with setting up the Raspberry Pi, then move on to developing our Avalonia application, and finally deploy it to run on the Pi. Let's get started.

Why Embedded Linux and Raspberry Pi?

The combination of embedded Linux systems, particularly the Raspberry Pi, with .NET offers several key advantages for developers. One of the most significant benefits is the simplified licensing model. Unlike the complex landscape of Embedded Windows with its multiple flavors and complex licensing agreements, Linux provides a straightforward, open-source approach. This simplification can lead to substantial cost savings in licensing fees alone.

Linux's ability to run efficiently on lower-powered devices compared to Windows provides developers with increased hardware flexibility. This efficiency allows for either reduced hardware costs or improved performance with the same hardware specifications. The Raspberry Pi exemplifies this, offering an affordable entry point into embedded development, with hardware costs starting much lower than comparable Windows-based systems.

An Avalonia App running on a Raspberry Pi

The versatility of Linux-based systems provides a flexible foundation for a wide range of applications, from simple IoT devices to complex control systems. This flexibility is complemented by the extensive community support for both Raspberry Pi and Linux. These large, active communities offer a wealth of resources, documentation, and third-party libraries, accelerating development and problem-solving.

Microsoft's cross-platform .NET enables full-featured .NET development on Linux. This allows developers to leverage their existing skills and code bases while taking advantage of the benefits of embedded Linux systems. The combination of reduced licensing costs, lower hardware requirements, and improved performance makes this an attractive option for many embedded projects, from industrial control systems to consumer devices.

Let's now look at how we can run our Avalonia application to the Pi.

Setting Up Your Raspberry Pi

The first step in our embedded Linux adventure is to set up the Raspberry Pi with Raspberry Pi OS Lite. This lightweight operating system provides an excellent foundation for our embedded .NET application.

Downloading Raspberry Pi OS Lite

Start by visiting the official Raspberry Pi website and downloading the Raspberry Pi OS Lite operating system image. This stripped-down version of Raspbian is perfect for headless applications and embedded projects.

Preparing Your Raspberry Pi

The setup process varies slightly depending on your Raspberry Pi model.

For Raspberry Pi 4 B

You'll need an SD card to install the operating system. Insert the SD card into your computer.

For Raspberry Pi Compute Module 4 (CM4)

You'll need an I/O board, such as the official Compute Module 4 IO board or alternatives like the SourceKit PiTray mini. Prepare the eMMC memory for mounting.

Flashing the Operating System:

  • Download and install the Etcher image writing utility.
  • Open Etcher and select the Raspberry Pi OS Lite .zip file you downloaded.
  • Choose the target storage (SD card or CM4 eMMC).
  • Click 'Flash!' to write the image.
  • Once flashing is complete, create an empty file named 'ssh' (without an extension) in the boot drive. This enables SSH access to your Raspberry Pi.

For CM4 users: Add the following line to /boot/config.txt to enable USB 2.0 ports:

dtoverlay=dwc2,dr_mode=host

First Boot and Library Installation

Power up your Raspberry Pi and log in. Then, update the system and install necessary libraries:

sudo apt update
sudo apt upgrade
sudo reboot
sudo apt-get install libgbm1 libgl1-mesa-dri libegl1-mesa libinput10

Verifying DRM (Optional)

To ensure your DRM setup is working correctly, you can install and run the kmscube tool:

sudo apt-get install kmscube
sudo kmscube

If successful, you should see a spinning cube on your Raspberry Pi's screen.

Preparing Your Avalonia Application for Embedded Linux

Now that our Raspberry Pi is set up, let's create an Avalonia application tailored for embedded Linux environments.

Creating a New Avalonia Application

Start by creating a new Avalonia application using your preferred development environment. For this tutorial, we'll call our project "AvaloniaRaspbianLiteDrm".

Adding the Avalonia.LinuxFramebuffer Package

To enable framebuffer support, add the following package to your project:

dotnet add package Avalonia.LinuxFramebuffer

Creating the MainView

When working with a framebuffer, we need a separate view (UserControl) to serve as our top-level control. Create a new UserControl named MainView with the following XAML:

<UserControl xmlns="https://github.com/avaloniaui"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
             mc:Ignorable="d"
             d:DesignWidth="800"
             d:DesignHeight="450"
             x:Class="AvaloniaRaspbianLiteDrm.MainView">
    <StackPanel HorizontalAlignment="Center"
                VerticalAlignment="Center"
                Margin="30"
                Spacing="30">
        <TextBlock FontSize="25">
            Welcome to Avalonia! The best XAML framework ever ♥
        </TextBlock>
        <Slider />
    </StackPanel>
</UserControl>

Creating the MainSingleView

Next, create a new UserControl named MainSingleView to host the MainView:

<UserControl xmlns="https://github.com/avaloniaui"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
             xmlns:avaloniaRaspbianLiteDrm="clr-namespace:AvaloniaRaspbianLiteDrm"
             mc:Ignorable="d"
             d:DesignWidth="800"
             d:DesignHeight="450"
             x:Class="AvaloniaRaspbianLiteDrm.MainSingleView">
    <avaloniaRaspbianLiteDrm:MainView />
</UserControl>

Updating MainWindow.axaml

Modify the MainWindow.axaml to host the MainView:

<Window xmlns="https://github.com/avaloniaui"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:avaloniaRaspbianLiteDrm="clr-namespace:AvaloniaRaspbianLiteDrm"
        mc:Ignorable="d"
        d:DesignWidth="800"
        d:DesignHeight="450"
        x:Class="AvaloniaRaspbianLiteDrm.MainWindow"
        Title="AvaloniaRaspbianLiteDrm">
    <avaloniaRaspbianLiteDrm:MainView />
</Window>

Preparing Program.cs

Update the Main method in Program.cs to enable DRM usage:

public static int Main(string[] args)
{
    var builder = BuildAvaloniaApp();
    if (args.Contains("--drm"))
    {
        SilenceConsole();
        return builder.StartLinuxDrm(args: args, card: null, scaling: 1.0);
    }

    return builder.StartWithClassicDesktopLifetime(args);
}

private static void SilenceConsole()
{
    new Thread(() =>
        {
            Console.CursorVisible = false;
            while (true)
                Console.ReadKey(true);
        })
        { IsBackground = true }.Start();
}

Updating App.axaml.cs

Modify the OnFrameworkInitializationCompleted method in App.axaml.cs:

public override void OnFrameworkInitializationCompleted()
{
    if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
        desktop.MainWindow = new MainWindow();
    else if (ApplicationLifetime is ISingleViewApplicationLifetime singleView)
        singleView.MainView = new MainSingleView();

    base.OnFrameworkInitializationCompleted();
}

Testing on Desktop

At this point, you can run and debug your application on your development machine to ensure everything is working correctly.

Deploying and Running on Raspberry Pi

Now that we have our Avalonia application ready, it's time to deploy and run it on our Raspberry Pi.

Publishing the Application

Use the following command to publish your application:

dotnet publish -c Release -o publish -r linux-arm -p:PublishReadyToRun=true -p:PublishSingleFile=true -p:PublishTrimmed=true --self-contained true -p:IncludeNativeLibrariesForSelfExtract=true

This command creates a self-contained, single-file application optimized for ARM-based Linux systems.

Copying the Application to Raspberry Pi

Transfer the published files from the /publish directory of your project to your Raspberry Pi. You can use various methods for this:

  • SCP command: scp <source> <destination>
  • File transfer applications like CyberDuck
  • Physical transfer via USB stick

Running the Application on Raspberry Pi

First, change the permissions of the application to make it executable:

sudo chmod +x /path/to/app/AvaloniaRaspbianLiteDrm

Then, run the application with the following command:

sudo ./path/to/app/AvaloniaRaspbianLiteDrm --drm

Your Avalonia application should now be running on your Raspberry Pi!

Advanced Topics and Considerations

As you continue to develop .NET applications for embedded Linux systems like the Raspberry Pi, consider the following advanced topics:

  • Performance Optimization: Embedded systems often have limited resources. Profile your application and optimize performance-critical sections. Consider using AOT compilation for improved startup times and overall performance.
  • Hardware Interfacing: Explore libraries like System.Device.Gpio to interact with GPIO pins on the Raspberry Pi, enabling your application to control hardware components.
  • Security: Implement proper security measures, especially if your embedded device is connected to the internet. Consider using encryption for sensitive data and implementing secure update mechanisms.
  • Remote Debugging: Set up remote debugging to streamline the development process when working with embedded devices.

Conclusion

We've covered a lot of ground in this guide, from setting up a Raspberry Pi with Raspberry Pi OS Lite to creating and deploying an Avalonia application using DRM. This process opens up new avenues for .NET developers interested in embedded systems.

The combination of .NET, Avalonia, and embedded Linux is powerful, but it's not without challenges. You'll likely encounter hardware limitations, performance bottlenecks, and integration issues along the way. That's part of the fun of embedded development.

As you move forward, don't be afraid to experiment. Try different hardware setups, push the limits of what your Raspberry Pi can do, and share your findings with the community. There's always more to learn in this field.

Good luck with your embedded .NET projects!

Latest Posts

Here’s what you might have missed.