This project shows how you can control hardware using Meadow.Foundation within an Avalonia application. In this particular example, we're going to get temperature, pressure and humidity readings from a BME688 along with a status LED, and show the values on an Avalonia window.
Meadow.Foundationa platform for quickly and easily building connected things using.NET on Meadow. Created by Wilderness Labs, it's completely open source and maintained by the Wilderness Labs community.
Step 1 - Assemble the circuitWire your project like this:
Create a new Avalonia project in Visual Studio 2022 for Windows and name it AvaloniaMeadow.
Step 3 - Add the required NuGet packagesFor this project, search and install the following NuGet packages:
- Meadow.Windows
- Meadow.Avalonia
- Meadow.Foundation.ICs.IOExpanders.Ft232h
- Meadow.Foundation.Sensors.Atmospheric.Bme68x
Add the native library (libmpsse.dll) of the FT232H IO Expander depending on your CPU's architecture (Win32 or x64) to your project and set the Copy to Output Directory to Copy if newer or Copy always.
App.axaml.cs
Copy the following code below:
public partial class App : AvaloniaMeadowApplication<Windows>
{
public override void OnFrameworkInitializationCompleted()
{
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
{
desktop.MainWindow = new MainWindow
{
DataContext = new MainWindowViewModel(),
};
}
base.OnFrameworkInitializationCompleted();
}
public override void Initialize()
{
AvaloniaXamlLoader.Load(this);
LoadMeadowOS();
}
public override Task MeadowInitialize()
{
var expander = new Ft232h();
var bme680 = new Bme680(expander.CreateSpiBus(), expander.Pins.C7);
Resolver.Services.Add(bme680);
var led = new Led(expander.Pins.C0);
Resolver.Services.Add<ILed>(led);
return Task.CompletedTask;
}
}
Its important to note that the App class extends from AvaloniaMeadowApplication<Windows>
, and in the Initialize()
method we call LoadMeadowOS();
to start running Meadow.OSwithin the MAUI app.\
We also override the MeadowInitialize()
method, which initializes any peripherals connected to your PC, in this case, instantiates the FT232H IO Expander to use its pins to initialize the BME688 environmental sensor and a LED as a status feedback.
Lastly, we add the BME688
sensor and LED to the Resolver.Servicesso we can get these object from anywhere in the app (in this case, a MainPageViewModel
).
MainPageViewModel.cs
Add a new MainPageViewModel
class to your project and copy the following:
public class MainWindowViewModel : ViewModelBase
{
private ILed _led;
private Bme680 _bme680;
private string _temperatureValue;
public string TemperatureValue
{
get => _temperatureValue;
set => this.RaiseAndSetIfChanged(ref _temperatureValue, value);
}
private string _humidityValue;
public string HumidityValue
{
get => _humidityValue;
set => this.RaiseAndSetIfChanged(ref _humidityValue, value);
}
private string _pressureValue;
public string PressureValue
{
get => _pressureValue;
set => this.RaiseAndSetIfChanged(ref _pressureValue, value);
}
public MainWindowViewModel()
{
TemperatureValue = "0°C";
HumidityValue = "0%";
PressureValue = "0atm";
// since Avalonia and Meadow are both starting at the same time, we must wait
// for MeadowInitialize to complete before the output port is ready
_ = Task.Run(WaitForHardware);
}
private async Task WaitForHardware()
{
while (_led == null || _bme680 == null)
{
_bme680 = Resolver.Services.Get<Bme680>();
_led = Resolver.Services.Get<ILed>();
await Task.Delay(100);
}
_bme680.Updated += Bme680Updated;
_bme680.StartUpdating();
}
private void Bme680Updated(object? sender, IChangeResult<(Temperature? Temperature, RelativeHumidity? Humidity, Pressure? Pressure, Resistance? GasResistance)> e)
{
_led.IsOn = true;
Thread.Sleep(1000);
TemperatureValue = $"{e.New.Temperature.Value.Celsius:n0}°C";
HumidityValue = $"{e.New.Humidity.Value.Percent:n0}%";
PressureValue = $"{e.New.Pressure.Value.StandardAtmosphere:n2}atm";
_led.IsOn = false;
}
}
A
few things to consider in this class:
WaitForHardware
- A fire and forget task invoked from the ViewModel's constructor. Since we're starting MeadowOS around the same time the Avalonia app starts, we cant know when the peripherals are initialized and can be referenced using the resolver, which is why we have a while loop checking when the BME680 and LED objects are retrieved and we can continue setting them up like start the sensor to read every 5 seconds.Bme688Updated
- Every 5 seconds, this event get triggered with the latest atmospheric readings, and we update the format the values toTemperatureValue
,HumidityValue
andPressureValue
string properties databound to the MainWindow view.
MainWindow.axaml
Copy the following XAML code below:
<Window
xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="using:AvaloniaMeadow.ViewModels"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
d:DesignWidth="500"
d:DesignHeight="600"
x:Class="AvaloniaMeadow.Views.MainWindow"
Icon="/Assets/avalonia-logo.ico"
Title="Meadow in Avalonia">
<Border
Background="#B35E2C"
BorderBrush="White"
BorderThickness="5">
<StackPanel
Spacing="30"
Margin="30"
Orientation="Vertical"
HorizontalAlignment="Center"
VerticalAlignment="Center">
<Border
BorderThickness="0"
HorizontalAlignment="Center"
BoxShadow="-5 5 20 0 #40000000">
<Image
Source="/Assets/meadow.png"
Height="200" />
</Border>
<TextBlock
Text="Meadow on Avalonia"
HorizontalAlignment="Center"
Foreground="White"
FontSize="32" />
<TextBlock
Text="Atmospheric readings from a BME688"
HorizontalAlignment="Center"
Foreground="White"
FontSize="22" />
<Border
Background="#EF7D3B"
BorderThickness="2"
CornerRadius="5"
Padding="4"
BoxShadow="-5 5 20 0 #40000000"
HorizontalAlignment="Center">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<TextBlock
Text="Temperature:"
Foreground="White"
FontSize="18"
Grid.Row="0"
Grid.Column="0"
Margin="5" />
<TextBlock
Text="Pressure:"
Foreground="White"
FontSize="18"
Grid.Row="1"
Grid.Column="0"
Margin="5" />
<TextBlock
Text="Humidity:"
Foreground="White"
FontSize="18"
Grid.Row="2"
Grid.Column="0"
Margin="5" />
<TextBlock
Text="{Binding TemperatureValue}"
Foreground="White"
FontSize="18"
Grid.Row="0"
Grid.Column="1"
Margin="20,5,5,5" />
<TextBlock
Text="{Binding HumidityValue}"
Foreground="White"
FontSize="18"
Grid.Row="2"
Grid.Column="1"
Margin="20,5,5,5" />
<TextBlock
Text="{Binding PressureValue}"
Foreground="White"
FontSize="18"
Grid.Row="1"
Grid.Column="1"
Margin="20,5,5,5" />
</Grid>
</Border>
</StackPanel>
</Border>
</Window>
This is the Avalonia app's UI that shows an Image of the circuit board with the FT232H IO expander and the BME680 and LED, along with a small window updating the atmospheric readings every 5 seconds.
Step 6 - Run the projectClick the Run button in Visual Studio. It should look like to the following GIF:
This project is only the tip of the iceberg in terms of the extensive exciting things you can do with Meadow.Foundation.
- It comes with a huge peripheral driver library with drivers for the most common sensors and peripherals.
- The peripheral drivers encapsulate the core logic and expose a simple, clean, modern API.
- This project is backed by a growing community that is constantly working on building cool connected things and are always excited to help new-comers and discuss new projects.
Comments