Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

Brewing Beer With Raspberry Pi: Building a Universal Windows Application

DZone's Guide to

Brewing Beer With Raspberry Pi: Building a Universal Windows Application

The latest part in the "Brewing Beer With Raspberry Pi" series, which takes us through developing a Windows Phone App to keep track of temperature.

· IoT Zone
Free Resource

Our beer cooling solution is not controllable through the IoT Hub but it reports data there. We can control our device through a simple command line application but that is not enough for us. In this post we start building a Universal Windows Application that helps us monitor temperatures, and then build a mobile application.

We can visualize the cooling process by connecting Excel to SQL Server or by using SQL Server Reporting Services (SSRS). Excel doesn’t show us real-time data if we don’t refresh a data source manually. SSRS is total overkill for our scenario and we won’t even consider it. As cooling may take hours we want something that we can take with us if we need to go out, and a mobile application is the perfect match for this need.

WARNING! I’m not a specialist on XAML and mobile applications and therefore please forgive me if my XAML or coding strategy isn't optimal. Please feel free to point out any mistakes.

Building Mobile Application

We want our application to be simple to use and we want it to show data. Let’s focus first on showing data. We build our application as a Windows 10 Universal Application to later support a desktop client.

The image shows what will be the end result. To focus here on visualizing data, we will leave out reporting of cooling rate and cooling solution status. Initial and current temperatures will come through Azure IoT Hub.

Our action plan here is simple:

  1. Create a model for main view.
  2. Create a main view with data and chart.
  3. Connect the application to Azure IoT Hub.

I think for one post this is more than enough. If we generate more ideas for the application then I can always write another post about it.

Creating a Measurement Model

We start with creating a model for our application to show temperatures on a chart. 

[DataContract]
public class Measurement
{
    [DataMember(Name = "deviceId")]
    public string DeviceId;

    [DataMember(Name = "batchKey")]
    public string BatchKey;

    [DataMember(Name = "timeStamp")]
    public DateTime Timestamp;

    [DataMember(Name = "beerTemp")]
    public double BeerTemperature;

    [DataMember(Name = "ambientTemp")]
    public double AmbientTemperature;

    [DataMember(Name = "estimate")]
    public double Estimate;
}

This is actually the same data we send out from the cooling solution background task.

Creating View Model

The next model we need is the view model that provides data to the application main page. We are using the INotifyPropertyChanged interface to notify the page about changes in property values. Property called Data holds temperature data and it is observable because we want to add more measurements to this collection when measurements arrive from Azure IoT Hub.

public class BatchViewModel : INotifyPropertyChanged
{
    public ObservableCollection<Measurement> Data { get; private set; }
    public event PropertyChangedEventHandler PropertyChanged;

    private string _batchKey;
    private double _initialBeerTemperature;
    private double _currentBeerTemperature;
    private double _ambientTemperature;
    private string _message;

    public BatchViewModel()
    {
        Data = new ObservableCollection<Measurement>();
    }

    public string Message
    {
        get { return _message; }
        set { SetField(ref _message, value); }
    }

    public string BatchKey
    {
        get { return _batchKey; }
        set { SetField(ref _batchKey, value); }
    }

    public double InitialBeerTemperature
    {
        get { return _initialBeerTemperature; }
        set { SetField(ref _initialBeerTemperature, value); }
    }

    public double CurrentBeerTemperature
    {
        get { return _currentBeerTemperature; }
        set { SetField(ref _currentBeerTemperature, value); }
    }

    public double AmbientTemperature
    {
        get { return _ambientTemperature; }
        set { SetField(ref _ambientTemperature, value); }
    }

    protected void SetField<T>(ref T field, T value, [CallerMemberName] string propertyName = null)
    {
        if (EqualityComparer<T>.Default.Equals(field, value))
            return;

        field = value;
        OnPropertyChanged(propertyName);
    }

    protected virtual void OnPropertyChanged(string propertyName)
    {
        if (PropertyChanged != null)
            PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
    }
}

Later we add Azure IoT Hub reading to this model but right now we want to finish the main page of the application.

Creating Main Page

Now let’s create the main page that displays our cooling chart. Add WinRTXamlToolkit.UWP and WinRTXamlToolkit.Controls.DataVisualization.UWP Nuget packages to the project, open MainPage.xaml and take the XAML given below.

<Page
    x:Class="BeerIoT.Windows10Client.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:BeerIoT.Windows10Client"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:DataVis="using:WinRTXamlToolkit.Controls.DataVisualization"
    xmlns:Charting="using:WinRTXamlToolkit.Controls.DataVisualization.Charting"
    mc:Ignorable="d">

  <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
    <StackPanel Margin="20,20,20,20">
      <TextBlock Text="Eisbock #1" FontFamily="Segoe" Margin="0,0,0,0" FontSize="40" />
      <Grid>
        <Grid.ColumnDefinitions>
          <ColumnDefinition Width="Auto" />
          <ColumnDefinition Width="*" />
        </Grid.ColumnDefinitions>
        <Grid.RowDefinitions>
          <RowDefinition />
          <RowDefinition />
          <RowDefinition />
          <RowDefinition />
          <RowDefinition />
        </Grid.RowDefinitions>

        <TextBlock Grid.Column="0" Grid.Row="0" Text="Status:" Margin="0,0,10,0" />
        <TextBlock Grid.Column="1" Grid.Row="0" Text="Measuring" />
        <TextBlock Grid.Column="0" Grid.Row="1" Text="k:" Margin="0,0,10,0" />
        <TextBlock Grid.Column="1" Grid.Row="1" Text="0.542" />
        <TextBlock Grid.Column="0" Grid.Row="2" Text="Air T:" Margin="0,0,10,0" />
        <TextBlock Grid.Column="1" Grid.Row="2" Text="-12°C" />
        <TextBlock Grid.Column="0" Grid.Row="3" Text="Beer T:" Margin="0,0,10,0" />
        <TextBlock Grid.Column="1" Grid.Row="3" Text="8°C" />
        <TextBlock Grid.Column="0" Grid.Row="4" Text="Time left" Margin="0,0,10,0" />
        <TextBlock Grid.Column="1" Grid.Row="4" Text="12h" />
      </Grid>

      <TextBlock Name="MsgText" TextWrapping="Wrap" Text="{Binding Message}"></TextBlock>

      <Charting:Chart Margin="0,20,0,0"  x:Name="LineChart" HorizontalAlignment="Left" VerticalAlignment="Top" 
        Title="Cooling chart" BorderThickness="1" BorderBrush="Black">
        <Charting:LineSeries Margin="0" ItemsSource="{Binding Data}" IndependentValuePath="Timestamp" 
          DependentValuePath="BeerTemperature" IsSelectionEnabled="True">
          <Charting:LineSeries.IndependentAxis>
            <Charting:DateTimeAxis IntervalType="Hours" Orientation="X">
            </Charting:DateTimeAxis>
          </Charting:LineSeries.IndependentAxis>
        </Charting:LineSeries>
        <Charting:Chart.LegendStyle>
          <Style TargetType="FrameworkElement">
            <Setter Property="Width" Value="0"/>
            <Setter Property="Height" Value="0"/>
          </Style>
        </Charting:Chart.LegendStyle>
        <Charting:Chart.Axes>
          <Charting:DateTimeAxis Title="t">
          </Charting:DateTimeAxis>
          <Charting:LinearAxis Title="T" />
        </Charting:Chart.Axes>
      </Charting:Chart>
    </StackPanel>
  </Grid>
</Page>

This is our page now. There are some fields for important data and then a chart that shows beer temperature.

public sealed partial class MainPage : Page
{
    public MainPage()
    {
        this.InitializeComponent();
        Loaded += MainPage_Loaded;
        SizeChanged += MainPage_SizeChanged;
    }

    private void MainPage_SizeChanged(object sender, SizeChangedEventArgs e)
    {
        LineChart.Width = e.NewSize.Width - 40;
        LineChart.Height = LineChart.Width;
    }

    private void MainPage_Loaded(object sender, RoutedEventArgs e)
    {
        DataContext = new BatchViewModel();
    }
}

Now we are done with our main page.

Connecting to Azure IoT Hub

Now comes the most complex part. As we can’t use convenient API libraries by Microsoft, we have to find another way around. We will be using Amqp.Net Lite AzureSBLite to read Azure IoT Hub as a regular Event Hub.

Add the following packages to the project:

  • Amqp,Net Lite
  • AzureSBLite
  • Newtonsoft.JSON

Add the following two constants to BatchViewModel class.

private const string connectionString = "SbConnectionAndSharedKey";
private const string _eventHubName = "YourEventHubName";

To read data from Azure IoT Hub, we need a method that runs on a separate thread. We can’t use a timer as we make blocking calls to service bus to read events. If we use a timer then it’s possible that timer callbacks will be piling up waiting for incoming messages, and we'll run out of resources.

private void ImportEvents()
{
    while (true)
    {
        try
        {
            var client = EventHubClient.CreateFromConnectionString(connectionString, _eventHubName);
            var group = client.GetDefaultConsumerGroup();

            for (var i = 0; i < 2; i++)
            {
                var receiver = group.CreateReceiver(i.ToString(), DateTime.UtcNow);

                EventData eventData = null;
                while ((eventData = receiver.Receive()) != null)
                {
                    var messageBytes = eventData.GetBytes();
                    var messageText = Encoding.ASCII.GetString(messageBytes);
                    var measurement = JsonConvert.DeserializeObject<Measurement>(messageText);

                    _taskFactory.StartNew(() => Message = messageText);
                    _taskFactory.StartNew(() => Data.Add(measurement));
                    eventData.Dispose();
                }

                receiver.Close();
            }
        }
        catch (Exception ex)
        {
            _taskFactory.StartNew(() => Message = ex.ToString());
        }
    }
}
We call this method out when view model is created but we call it as a task on separate thread so it doesn’t block user interface.

public BatchViewModel()
{
    Data = new ObservableCollection<Measurement>();

    _taskFactory = new TaskFactory(TaskScheduler.FromCurrentSynchronizationContext());

    _randomizer = new Random();
    Task.Run(() => ImportEvents());
}

This is it. Now we are done with our simple mobile application and it’s time try out how it works.

Testing the Mobile Application

As we can’t run an IoT application and Windows Phone application at the same time on Visual Studio debugger we have to choose one. I prefer to run the background service on debugger.

Let’s go step by step:

  1. Run Stream Analytics job.
  2. Build mobile application and deploy it to your Windows Phone 10.
  3. Run mobile application.
  4. Run RaspberryPI background process on Visual Studio debugger,
  5. Watch Windows Phone application, adding new data points to the chart.

Wrapping Up

Now we have our custom Windows 10 universal application to visualize the sensors data. We can modify the main page to make the application pretty on Windows 10 desktop machines. There are many ways how to improve and update this application, but the basic work is now done and we can use this application as is.

Topics:
internet of things ,raspberry pi ,iot

Published at DZone with permission of Gunnar Peipman, DZone MVB. See the original article here.

Opinions expressed by DZone contributors are their own.

THE DZONE NEWSLETTER

Dev Resources & Solutions Straight to Your Inbox

Thanks for subscribing!

Awesome! Check your inbox to verify your email so you can start receiving the latest in tech news and resources.

X

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}