Raspberry Pi Internet Thermometer with .NET & Azure, part 2/2

Make sure to check post 1 of 2, which describes how to connect your Raspberry Pi to a DS18B20 temperature sensor and read the sensor values using a .NET console application. In this post, will build on that sample and hook up the Raspberry Pi to the cloud where we’ll store the historical temperatures. Then we’ll quickly create a Windows Phone application to display the current temperature. Here’s a small video of everything in action:

We’ll be using an Azure Mobile Service because it’s one of the quickest ways to build and expose an API, together with a backend database. In real life, when you have maybe hundreds or thousands of sensors submitting data, you probably want to consider using a cloud service better suited for this scenario, like Azure Event Hubs (more on that in a later post). All the source code can be found on GitHub.

Step 1: build the cloud service to connect to

Fire up the Azure Portal in your favorite browser and log in. To add a new Mobile Service, click on the + NEW link at the bottom left, and select Mobile Service in the Compute category.

rasp_mobileservice_1

rasp_mobileservice_2In the first step of the wizard pick a URL for your Mobile Service (I choose cloudthermometer). Then select what type of database you want, an on which server, I went for a free database of 20MB, hosted in North Europe. Leave the Backend option set to Javascript (it’s sufficient for this basic sample) and click the next arrow. In the last step of the wizard you can choose the details of the database which will be created, when done click the Complete button. Now Azure is going to provision the service and its database, sit back and wait till the status is Ready.

Now navigate to the newly created Mobile Service by clicking on its name in the Azure Portal. We are going to add a table straight away, so click on the Data tab and then Add a Table. Name the table sensorvalue and leave all permissions to Anybody with the Application Key. Finish the wizard so the table will be created. We are not going to create any columns just yet, one of the cool things of Azure Mobile Services is that it supports Dynamic Schema creation during your development cycle. So when we are going to write to code to connect to the service, the database schema and corresponding REST API will be automatically created based on what data we’ll send to it.

Before we dive into the code of the application that will send data to this service, we need to do one thing in the Azure Portal: copy the Application Key to the clipboard. The easiest thing to do is to click on the little blue cloud icon right below the name of your service (and before the Dashboard tab). Expand the Connect an existing Windows or Windows Phone app section and copy the C# code snippet to the clipboard (we’ll use it in the next step).

rasp_mobileservice_5

Step 2: build the application to connect to the service

Ok, the Mobile Service is ready to receive our temperature data, now it’s time to create the application which will be running on the Raspberry Pi, which will read the temperature sensor data and send it to the cloud. You can continue on the application created in the previous part, or you can just create a new C# Console Application in Visual Studio (and make sure to target .NET Framework 4.5).

Add the NuGet package to the project called WindowsAzure.MobileServices, this will allow us to easily connect to the Mobile Service. The piece of code you copied to the clipboard in step 1, can be pasted into the Program class. This code defines the static member MobileService, which connects to our API based on the URL and Application Key.

class Program
{
    public static MobileServiceClient MobileService = new MobileServiceClient(
        "https://cloudthermometer.azure-mobile.net/",
        "CETpZxkiFLxhQCnZvXKkqgllxDpNew44"
    );

    ...
}

The next step is to create our data class, so add a new Class to the project, make it public and name it SensorValue. Define following properties:

public class SensorValue
{
    public string Id { get; set; }
    public string SensorName { get; set; }
    public double Value { get; set; }
    public DateTime Timestamp { get; set; }
}

In the Program class, add a using statement for the System.IO namespace and create a static function called GetTemperature based on the code snippet below. Notice that this function has one index parameter, this is because you can have multiple DS18B20 sensors on the same bus and the index parameter will allow you to specify which sensor you want to use.

static double GetTemperature(int sensorIndex)
{
    DirectoryInfo devicesDir = new DirectoryInfo("/sys/bus/w1/devices");
    var deviceDir = devicesDir.GetDirectories("28*")[sensorIndex];
    using (TextReader reader = 
        new System.IO.StreamReader(deviceDir.FullName + "/w1_slave"))
    {
        string w1slavetext = reader.ReadToEnd();

        string temptext =
            w1slavetext.Split(new string[] { "t=" }, 
            StringSplitOptions.RemoveEmptyEntries)[1];

        double temp = double.Parse(temptext) / 1000;
        return temp;
    }
}

Now let’s write the code that will actually send the temperatures to the Mobile Service:

static void Main(string[] args)
{
    System.Timers.Timer t = new System.Timers.Timer(2000);
    t.Elapsed += (s, e) =>
    {
        Console.Write("Sending ... ");
        double temp = GetTemperature(0);
        Console.Write(temp.ToString());
        MobileService.GetTable<SensorValue>().InsertAsync(
          new SensorValue()
              {
                  SensorName = "Raspberry Pi",
                  Timestamp = DateTime.Now,
                  Value = temp
              }
            );
        Console.WriteLine(" ... done!");
        t.Enabled = true;
    };

    t.AutoReset = false;
    t.Start();
    Console.ReadKey();
    t.Stop();
}

In the code snippet above, I’m using a Timer that fires every 2000 milliseconds (=2 seconds). When the interval elapses, we use the MobileService reference to access the table for the SensorValue class. By convention (they have the same name) this class will be mapped to the table sensorvalue we created in step 1. Once we’ve got the table reference, we can use the InsertAsync method to create a new item. Remember when creating the Mobile Service table, we didn’t specify any columns. If you run the app and then would go back to that Mobile Service in the Azure Portal, you’d notice the sensorvalue table does contain our columns (SensorName, Timestamp and Value): that’s the Dynamic Schema in action. When you’d specify another property in the SensorValue class and do an insert (or update) again, the Dynamic Schema feature would automatically add the corresponding column. Pretty cool and comes in quite handy in you don’t exactly know upfront which columns you need.

Finally build the code, and copy the resulting assembly (.EXE) and all the referenced assemblies you can find in the output folder to your Raspberry Pi. When you run it with Mono, the output should look as below. The small spike you see in the temperatures below, is me touching the sensor with my fingers. As you can see the DS18B20 pretty quick to react. When you go back to the Azure Portal, and navigate to the sensorvalue table on your Mobile Service, you should be able to find all the posted data.

rasp_mobileservice_6

Step 3: build a mobile application

rasp_mobileservice_7Seeing the temperature output in a Console Application and in a table is nice, but of course being able to check a nice mobile app would be way cooler. Let’s create a quick-and-dirty one by starting a new Blank App (in the Windows Phone Apps section in Visual Studio). Add the WindowsAzure.MobileServices NuGet package like we did before, and copy/paste the MobileService member again, this time in the App class (open the App.xaml.cs file). I’ll be using Diederik Krol’s excellent Gauge control to visualize the temperature, so add the WinRTXamlToolkit.Controls.Gauge.WindowsPhone package too. In the MainPage XAML, add the following line in the Grid element:

<Grid>
    <tk:Gauge Name="gauge"></tk:Gauge>
</Grid>

Then switch to the code of the MainPage class and add the snippet below to the MainPage constructor (after the two lines which are already there). The snippet will create a DispatcherTimer which will execute every second, and fetches the latest SensorValue from the Mobile Service so it can be displayed in the Gauge control.

public MainPage()
{
    this.InitializeComponent();
    this.NavigationCacheMode = NavigationCacheMode.Required;

    gauge.Unit = "°C";
    gauge.ValueStringFormat = "0.00";
    
    DispatcherTimer timer = new DispatcherTimer();
    timer.Interval = new TimeSpan(0, 0, 1);

    timer.Tick += async (s, e) =>
    {
        var sensorvalues = (from sv in App.MobileService.GetTable<SensorValue>()
                            orderby sv.Timestamp descending
                            select sv.Value).Take(1);

        var sensorvalue = (await sensorvalues.ToListAsync()).FirstOrDefault();

        gauge.Value = sensorvalue;
    };
    timer.Start();
}

Summary and how to get started

Once again, the code and set-up discussed in my posts were just to get you started and excited about what you can accomplish nowadays as a developer. Here and there I cut some corners to make it easy to understand, like for example choosing a Mobile Service instead of lets say an Event Hub. If you would like to try this yourself, but you don’t have an Azure subscription and/or you don’t have Visual Studio; then there is some good news for you! First of all, you can sign up for a free Azure trial which gives you €150 worth of Azure consumption. Secondly you can use our brand new Visual Studio Community edition, which is the full version of Visual Studio, made free individuals, open source contributions and small companies.

Above all, have fun with this technology and remember, it has never been a better time to be a software developer!

Recommended resources:

  37 comments for “Raspberry Pi Internet Thermometer with .NET & Azure, part 2/2

  1. November 28, 2014 at 7:57 am

    Awesome post! Thanks a lot.

    • Aziz
      December 9, 2014 at 2:12 pm

      This is cool. Thanks for sharing.

    • Hans Weiger
      February 14, 2015 at 11:47 am

      Great article. I get an exception, on the raspberry pi that the System.Net.Http.dll Version 1.5.0.0 or a dependancy could not be found. What can be the Problem?

      • jan
        February 14, 2015 at 5:01 pm

        Do you have the latest version of Mono? (check by typing mono –version in a prompt.

        • Hans Weiger
          February 14, 2015 at 11:02 pm

          Thank you for your answer. I have installed Version 3.2.8. Here is the complete output of mono -V:
          Mono JIT compiler version 3.2.8 (Debian 3.2.8+dfsg-4+rpi1)
          Copyright (C) 2002-2014 Novell, Inc, Xamarin Inc and Contributors. http://www.mono-project.com
          TLS: __thread
          SIGSEGV: normal
          Notifications: epoll
          Architecture: armel,vfp+hard
          Disabled: none
          Misc: softdebug
          LLVM: supported, not enabled.
          GC: sgen

        • Hans Weiger
          February 15, 2015 at 12:49 pm

          So now I am trying to install Version 3.12 of mono I used the following three commands to get the new version:
          sudo -i
          apt-key adv –keyserver keyserver.ubuntu.com –recv-keys 3FA7E0328081BFF6A14DA29AA6A19B38D3D831EF
          echo deb http://download.mono-project.com/repo/debian wheezy main | sudo tee /etc/apt/sources.list.d/mono-xamarin.list
          echo deb http://download.mono-project.com/repo/debian wheezy-apache24-compat main | sudo tee -a /etc/apt/sources.list.d/mono-xamarin.list

          I have also downloaded the latest version of respian and did a apt-get update and upgrade but when I want to install mono with apt-get install mono-complete I get an errror that the libglib2.0-dev could not be installed.

          • jan
            February 15, 2015 at 6:23 pm

            I think you missed some quotes in the echo lines.

          • Hans Weiger
            February 15, 2015 at 8:24 pm

            I have tried it with quotes, it didn`t find the Version 3.12 at all. In your “getting started with the raspberry pi ..” article someone commented that the quotes didn´t work. Without quotes I saw that the apt-get update did find the 3.12. Version but as I said the Installation didn´t work. Now I have started to compile mono on the rasperry. It will take the next 12 hours. I will tell tomorrow if it worked. Thanks for your help and one again thank you for sharing I got many ideas.

          • Hans Weiger
            February 16, 2015 at 9:02 pm

            Compile didn´t work, tomorrow morning I only got hieroglyphics on the screen. On the internet I found a compiled 3.10.0 version of mono for the raspberry pi. I was able to install that, but when I execute my program I get the following exception:

            Unhandled Exception:
            System.TypeInitializationException: An exception was thrown by the type initializer for Test18DS20.Program —> System.IO.FileNotFoundException: Could not load file or assembly ‘System.Net.Http, Version=1.5.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a’ or one of its dependencies.
            File name: ‘System.Net.Http, Version=1.5.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a’
            — End of inner exception stack trace —
            [ERROR] FATAL UNHANDLED EXCEPTION: System.TypeInitializationException: An exception was thrown by the type initializer for Test18DS20.Program —> System.IO.FileNotFoundException: Could not load file or assembly ‘System.Net.Http, Version=1.5.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a’ or one of its dependencies.
            File name: ‘System.Net.Http, Version=1.5.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a’
            — End of inner exception stack trace —

            The following dlls are copied with the exe:
            Microsoft.WindowsAzure.Mobile.dll Version 1.3.30130.0
            Microsoft.WindowsAzure.Mobile.Ext.dll Version 1.3.30130.0
            Newtonsoft.Json.dll Version 6.0.4.17603
            System.Net.Http.Extensions.dll Version 2.2.28.0
            System.Net.Http.Primitives.dll Version 4.2.28.0

            Perhaps you could give me a hint what could be wrong.

            What version of mono do you have installed?

          • Hans Weiger
            February 17, 2015 at 8:20 pm

            Finally I got it working. I have downloaded your source code and the difference was the app.config. When I did the implement the program step by step I finally go an app.config file without the following:

            As I inserted this, the execption that the System.Net.Http Version 1.5.0.0 is missing was gone. But it still doesn´t send values to the mobile Service. I had to insert the ssl certificates that mono can make SSL Connections:
            sudo mozroots –import –machine –sync
            sudo certmgr -ssl -m https://go.microsoft.com
            sudo certmgr -ssl -m https://nugetgallery.blob.core.windows.net
            sudo certmgr -ssl -m https://nuget.org

            Perhaps someone has a similar Problem and this could help

          • Hans Weiger
            February 17, 2015 at 8:39 pm

            I saw that I forgot to paste the code of the app.config:

            dependentAssembly>

  2. jan
    February 19, 2015 at 7:19 am

    Hi Hans, I noticed this yesterday too. It seems that there has been a new version of the NuGet package for Mobile Services has been released (which needs some extra steps to get it working on Mono). I’ll try to write a how-to post summarizing everything. 🙂

  3. jan
    February 19, 2015 at 7:19 am

    Hi Hans, I noticed this yesterday too. It seems that there has been a new version of the NuGet package for Mobile Services has been released (which needs some extra steps to get it working on Mono). I’ll try to write a how-to post summarizing everything. 🙂

  4. Diego Liñan
    February 21, 2015 at 12:29 am

    How did you exactly manage to install version 3.12.0 of mono on your rasperry? whenever I try to do it I get tons of errors of missing or uncompilable libraries. Here’s a list of them:

    libglib2.0-cil

    libgtk2.0-cil

    libglade2.0-cil

    libnunit-core-interfaces2.6.3-cil

    libnunit-core2.6.3-cil

    libnunit-util2.6.3-cil

    libnunit-console-runner2.6.3-cil

    libnunit-framework2.6.3-cil

    libnunit-mocks2.6.3-cil

    libnunit-cil-dev

    libmono-cil-dev

    libwebkit1.1-cil

    mono-devel

    mono-complete

    monodoc-browser

  5. Diego Liñan
    February 21, 2015 at 12:29 am

    How did you exactly manage to install version 3.12.0 of mono on your rasperry? whenever I try to do it I get tons of errors of missing or uncompilable libraries. Here’s a list of them:

    libglib2.0-cil

    libgtk2.0-cil

    libglade2.0-cil

    libnunit-core-interfaces2.6.3-cil

    libnunit-core2.6.3-cil

    libnunit-util2.6.3-cil

    libnunit-console-runner2.6.3-cil

    libnunit-framework2.6.3-cil

    libnunit-mocks2.6.3-cil

    libnunit-cil-dev

    libmono-cil-dev

    libwebkit1.1-cil

    mono-devel

    mono-complete

    monodoc-browser

    • Diego Liñan
      February 22, 2015 at 12:20 am

      Hi Jan!
      Yes I saw that one, but those errors appear while doing it the way you suggest… Mind telling me which revision o Raspbian and Mono you are using? With the latest Raspbian and the latest Mono I get those errors…

      • jan
        March 1, 2015 at 8:41 pm

        I’m running on a Pi 2 with the latest version of Raspbian. You have a v2 too?

    • Diego Liñan
      February 22, 2015 at 12:20 am

      Hi Jan!
      Yes I saw that one, but those errors appear while doing it the way you suggest… Mind telling me which revision o Raspbian and Mono you are using? With the latest Raspbian and the latest Mono I get those errors…

      • jan
        March 1, 2015 at 8:41 pm

        I’m running on a Pi 2 with the latest version of Raspbian. You have a v2 too?

  6. Ben
    February 28, 2015 at 8:04 pm

    Hi Jan,

    I have the identical issue as Diego Liñan.
    I followed the instructions exactly as per http://j.tlns.be/2015/02/getting-started-with-the-raspberry-pi-2-for-net-developers/

    Hardware: Raspberry Pi B+
    Image: Raspbian (SHA-1 = b71d7b61f44e9bd582df71c9be494c271c97650f)

    What is the SHA-1 crc of the debian image OS image that you are using?

  7. Ben
    February 28, 2015 at 8:04 pm

    Hi Jan,

    I have the identical issue as Diego Liñan.
    I followed the instructions exactly as per http://j.tlns.be/2015/02/getting-started-with-the-raspberry-pi-2-for-net-developers/

    Hardware: Raspberry Pi B+
    Image: Raspbian (SHA-1 = b71d7b61f44e9bd582df71c9be494c271c97650f)

    What is the SHA-1 crc of the debian image OS image that you are using?

  8. Jay J
    June 3, 2015 at 7:35 am

    Hi Jens,

    I’m getting the following error, any help would be greatly appreciated!

    Unhandled Exception:
    System.TypeInitializationException: An exception was thrown by the type initializer for rpitemp.Program —> System.TypeInitializationException: An exception was thrown by the type initializer for Microsoft.WindowsAzure.MobileServices.MobileServiceClient —> System.InvalidProgramException: Invalid IL code in System.Net.Http.HttpMethod:get_Post (): method body is empty.

    at Microsoft.WindowsAzure.MobileServices.MobileServiceClient..cctor () [0x00000] in :0
    — End of inner exception stack trace —
    at rpitemp.Program..cctor () [0x00000] in :0
    — End of inner exception stack trace —
    [ERROR] FATAL UNHANDLED EXCEPTION: System.TypeInitializationException: An exception was thrown by the type initializer for rpitemp.Program —> System.TypeInitializationException: An exception was thrown by the type initializer for Microsoft.WindowsAzure.MobileServices.MobileServiceClient —> System.InvalidProgramException: Invalid IL code in System.Net.Http.HttpMethod:get_Post (): method body is empty.

    at Microsoft.WindowsAzure.MobileServices.MobileServiceClient..cctor () [0x00000] in :0
    — End of inner exception stack trace —
    at rpitemp.Program..cctor () [0x00000] in :0
    — End of inner exception stack trace —

    Regards,
    Jay

  9. Pavel
    July 24, 2015 at 11:32 am

    Hi perfect article. But I have one small problem. After I done your manual my Win App display nothing (only blank black screen). I have no errors on program. Could you tell me where would be mistake?

Leave a Reply

Your email address will not be published. Required fields are marked *