Freigeben über


Prototyping a connected object using the .NET Gadgeteer: the example of a steampunk meteo station

clip_image002[6]

For years I’ve had this very old voltmeter I wanted to do something with: I liked the beautiful wooden case, the leather strap, but honestly, I had better tools for my day to day electronics experiments. I found a purpose for it when I started experimenting with the .NET Gadgeteer kit, and one of my first prototype of connected object: a simple meteo station that would measure temperature, humidity and pressure and upload data to a feed in Cosm, formerly known as Pachube, which is a connected objects web platform used to publish and consume realtime data. I had two goals with this project: have fun with the .NET Gadgeteer kits, and experiment on a complete connected object prototyping cycle (I’m not going to talk about industrialization or mass production here).

Here’s the plan:

  1. Discovering the .NET Gadgeteer tooling, and interfacing with sensors
  2. Connecting to Cosm (Pachube)
  3. Creating the steampunk GUI
  4. Making the meteo station user-configurable using a small webserver
  5. Prototyping the finished design using 3D modeling tools

 

>> If you want to download the whole article in a Word document, here’s the link. <<

Discovering the .NET Gadgeteer tooling, and interfacing with sensors

The .NET Gadgeteer is a rapid prototyping environment for connected objects. Kits are manufactured by Microsoft partners, like GHI Electronics (https://www.ghielectronics.com), Seed Studio (https://www.seeedstudio.com/depot/), or the Mountaineer Group (https://www.mountaineer.org/). The core libraries (based on the .NET Micro Framework: (https://www.netmf.com)) are maintained by Microsoft and the community, while the hardware specific drivers and updaters are maintained by the different partner.

Therefore, there are 3 different packages to install on top of Visual Studio 2010 (any version, Express will do) to install the .NET Gadgeteer tools: the .NET Micro Framework base, the Gadgeteer Core, and the hardware specific SDK. In our case, we are going to use a FEZ Spider Kit from GHI Electronics as well as a couple of sensors and a Wifi module.

Once you have installed everything, you can create your first .NET Gadgeteer application. You are then presented with a “designer” view of your project: at this time there’s only one motherboard in the designer. Using the toolbox, you can add the modules you are going to use. Once you have all the modules you need, you can either click on the connectors to wire them manually or right-click anywhere on the designer and select “Connect all modules”. If you don’t know anything about hardware, this will show you how to connect modules to the motherboard. Once this is done, you’re ready to start coding!

clip_image004[6]

Click on Program.cs to dive into the source code. At this point there’s only the main entry point, and a debug instruction. However all the objects needed to control the hardware have already been instantiated (in the Program.Generated.Cs file) and can be used right away. For example, if you have a temperature sensor, you can directly subscribe to the MeasurementComplete event, that will fire whenever a new measure is ready.

In our case, we are going to use a timer to regularly measure the meteorological parameters (temperature, pressure, and humidity). Everytime the timer fires, we will ask the different sensors to start measuring and they will notify us through their respective MeasurementComplete event that a new measure is ready.

First register for sensor events:

 temperatureHumidity.MeasurementComplete += new TemperatureHumidity.MeasurementCompleteEventHandler(temperatureHumidity_MeasurementComplete);
barometer.MeasurementComplete += new Barometer.MeasurementCompleteEventHandler(barometer_MeasurementComplete);

 

Then instantiate the timer and start it:

 GT.Timer SensorTimer = new GT.Timer(TimeSpan.FromTicks(TimeSpan.TicksPerMinute));
SensorTimer.Tick += new GT.Timer.TickEventHandler(timer_Tick);
SensorTimer.Start();

And here’s the timer handler code:

 

 void timer_Tick(GT.Timer timer)
{
    barometer.RequestMeasurement();
    temperatureHumidity.RequestMeasurement();
}

At this point, you already have an object that regularly measure meteorological parameters. You can compile and run it directly on the .NET Gadgeteer kit by connecting it to a USB port on your PC. It’s not connected though; we will handle that in the next part.

Connecting to Cosm

Before connecting to Cosm, we need to connect to a network. GHI Electronics offer different modules, we’ll talk about the Wifi Module, and the Ethernet (J11D) module .When purchasing anyone of them, pay attention that there are different references depending on the motherboard you’re using. Read the docs!

The Ethernet module is the easier to program: here’s the code to connect to a network using DHCP:

 ethernet.NetworkSettings.EnableDhcp();

The static equivalent:

 ethernet.NetworkSettings.EnableStaticIP("192.168.0.10", "255.255.255.0", "192.168.0.1");
string[] dnsServers = { "192.168.0.1", "192.168.0.2" };
ethernet.NetworkSettings.EnableStaticDns(dnsServers);

There are a couple of events you can subscribe to be notified about Network changes:

 ethernet.NetworkDown += new GTM.Module.NetworkModule.NetworkEventHandler(ethernet_NetworkDown);
ethernet.NetworkUp += new GTM.Module.NetworkModule.NetworkEventHandler(ethernet_NetworkUp);

The Wifi module is also very straightforward to configure: you are going to use the same code, only you need to join a network first:

 WiFi_RS21.WiFiNetworkInfo wifiInfo = wifi.Search("<SSID>");
wifi.Join(wifiInfo, "<KEY>");

The second step is to set the system time. Since there’s no “backup” battery that saves the clock time, one has to set it everytime the system is powered. This is done with a small NTP library called MicroNTP extracted from the Micro Framework Toolkit: https://mftoolkit.codeplex.com/

 DateTime UtcTime = NtpClient.GetNetworkTime("0.fr.pool.ntp.org");
Microsoft.SPOT.Hardware.Utility.SetLocalTime(UtcTime + new TimeSpan(TimeSpan.TicksPerHour * 2));

The NTP server returns the Coordinated Universal Time (UTC) therefore I add the timezone difference (+2H, hardcoded here for readability reasons, it would have to be a system parameter, we’ll see later how to create a settings system.

Once you’re connected to a network you need to send (and eventually, receive) data from Cosm (Pachube). It’s using a simple REST API and JSON serialization… you just have to pass your API key in the headers. Here’s how to send a simple webrequest posting data to your feed:

 public void Post(IFeedItem item)
{
    HttpWebRequest req = (HttpWebRequest)WebRequest.Create(this.AssociatedAccount.EventsUrl + this.Id.ToString());
    req.Method = "PUT";
    req.ContentType = "application/json";
    req.UserAgent = "NetduinoPlus";
    req.Headers.Add("X-PachubeApiKey", this.AssociatedAccount.ApiKey);
    req.Timeout = 1000;
    if (this.AssociatedAccount.HttpProxy != null)
        req.Proxy = this.AssociatedAccount.HttpProxy;

    string content = item.ToJson(this.Id);
    Debug.Print(content);


    byte[] postdata = System.Text.Encoding.UTF8.GetBytes(content);
    req.ContentLength = postdata.Length;

    try
    {
        using (Stream s = req.GetRequestStream())
        {
            s.Write(postdata, 0, postdata.Length);
        }

        using (WebResponse resp = req.GetResponse())
        {

            using (Stream respStream = resp.GetResponseStream())
            {
                byte[] respBytes = new byte[respStream.Length];

                int length = respStream.Read(respBytes, 0, respBytes.Length);

                string respString = new string(System.Text.Encoding.UTF8.GetChars(respBytes));
                Debug.Print(respString);
            }
        }
    }
    catch (Exception ex)
    {
        Debug.Print("exception : " + ex.Message);
    }
    finally
    {
        req.Dispose();
    }
}

 

I’m not going to go into many details about how Cosm works. Most of these networks (like OpenSenSe) have the same kind of APIs so feel free to adapt them. For your convenience I’ve wrapped my code into a very simple nuget package that you can find here:

 

-
https://nuget.org/packages/PachubeDAL-NETMF/1.0.2

 

And the sources are on codeplex:

-
https://pachubedal.codeplex.com/

 

The use of this package is really easy: first, instantiate an Account object and pass it your API key. Then you can create as many feeds as you want (I only have one here, with 3 datasets), and use the Post() method to send directly data to it:

 Account pachube = new Account("miQnUMICT-KjWhDwOxWQuycKTiuSAKxCWGgrdTNyaUNEND0g");
Feed meteoFeed = new Feed(44741, pachube);

      

 meteoFeed.Post(new DoubleFeedItem("barometer", sensorData.Pressure));

There you have it – a connected meteo station, in a few lines of code. Now, let’s make use of that screen!

Creating the Steampunk GUI : reading and displaying images from the SD Card, and charting data.

There are two ways to create a user interface using the .NET Micro Framework. Either using the very small subset of WPF, or, a bit like a game, create a display loop and use graphical elements and fonts, just like a game. In my case, I really want my interface to have a steampunk feeling so I’m going to use a lot of images. Since it will be sealed in a box, I have no need for touch interactions, so I’m going to go for the game-like approach. I’ll have a timer that will regularly draw all images, in the right order and at the right place, depending on the various data I have. The only “non-image” data that I’m going to display is a graph, and for that all I need is a SetPixel() method. It’s very easy.

The first step of the creation of the user interface was to work with a designer (Michel, who’s part of my team) to create a bitmap of the finished interface. Then, Michel generated all the images necessary to compose the interfaces: all the numbers, the different meteorological symbols, the fake copper plate, etc.

I will store the graphical assets on an SD Card, and load them at startup : here’s how to check that the SD Card is present and mounted:

 if (sdCard.IsCardInserted)
{
    if (!sdCard.IsCardMounted)
        sdCard.MountSDCard();

    Debug.Print("sd card mounted");
}

And the files are accessed this way:

 var storage = sdCard.GetStorageDevice();

for (int i = 0; i < 10; i++)
{
    Numbers[i] = storage.LoadBitmap(i.ToString() + ".bmp", Bitmap.BitmapImageType.Bmp);
}

Then there is a very simple API to display an image:

 display.SimpleGraphics.DisplayImage(MetalCover, 0, 0);

The only thing I have to do to build my user interface is to combine them in the right order to draw the bitmaps on top of one another. The problem is that, as you have correctly read, I’m using… bitmaps. .NET Micro Framework only supports JPEG, GIF and Bitmap images . Luckily for me the Bitmap class has a method that I can call to specify that a specific color should be treated as transparent… so I can lay a square bitmaps on top of one another and using a specific color (a really ugly pink color in my case) give them a square look: here’s an example of a clock number, and the corresponding transparency API:

clip_image005[6]

 Color Transparent = ColorUtility.ColorFromRGB(255, 0, 255);
MetalCover = storage.LoadBitmap("FondTransp.bmp", Bitmap.BitmapImageType.Bmp);
MetalCover.MakeTransparent(Transparent);

The trick with such a technique (which is not very elegant, but quit effective) to build an interface is to have pixel perfect assets and placements. Work with your designer and be precise!

The last thing is to draw the graph. It’s not very complicated, either algorithmically or technically: I just store a list of measures in memory, discarding them as new ones come in, much like a queue with a fixed length. Then I go through every element, drawing them backwards from the latest one, and making sure I never get out of the rectangle in which I’m allowed to draw. Here’s the code for the whole Graph class:

 class Graph
{
    public ArrayList Values;

    public double Max;
    public double Min;


    public Graph(int Capacity)
    {
        Values = new ArrayList();
        Values.Capacity = Capacity;
    }

    public void Add(double measure)
    {
        if (Values.Count == Values.Capacity)
        {
            Values.RemoveAt(0);
        }

        Values.Add(measure);
        UpdateMinMax();
    }

    private void UpdateMinMax()
    {
        int index = Values.Count - 1;

        this.Min = (double)Values[index] - 3.0;
        this.Max = (double)Values[index] + 3.0;
    }

    public void Draw(Display_T35 display, uint x, uint y, uint height) // width == capacity
    {
        double a,b;
        uint offset;

        if (Max > Min)
            a = -(height / (Max - Min));
        else
            a = 0;

        b = y - a * Max;

        for (int i = 0; i < Values.Count; i++)
        {
            int index = Values.Count - 1 - i;

            if (a == 0)
                offset = y + (height / 2);
            else
                offset = (uint)System.Math.Round(a * (double)Values[index] + b);

            DrawPoint(display, (uint)(x + Values.Capacity - i), offset);
        }
    }

    public void DrawPoint(Display_T35 display, uint x, uint y)
    {
        display.SimpleGraphics.SetPixel(Gadgeteer.Color.Black, x, y);
        display.SimpleGraphics.SetPixel(Gadgeteer.Color.Black, x + 1, y);
        display.SimpleGraphics.SetPixel(Gadgeteer.Color.Black, x, y + 1);
        display.SimpleGraphics.SetPixel(Gadgeteer.Color.Black, x + 1, y + 1);
    }

    public double Mean(int Count)
    {
        if (Count > 0 && Values.Count > 0)
        {
            var max = (Count > Values.Count) ? Values.Count : Count;

            double tmp = 0.0;
            for (int i = 0; i < max; i++)
            {
                tmp += (double)Values[max - i - 1];
            }
            tmp /= max;

            return tmp;
        }
        else
            return 0;
    }
}

 

You may also see that I have a vertical bitmap indicating the latest value of measured temperature: Placing it on the interface requires a small trick: since it’s very high, it’s far larger than the screen, so using simple maths to know, according to the temperature, which section I need, I just cut a small part of it and display this small part: here’s how I do it:

 int pixel = (int)(System.Math.Round((double)(40 - Status.Temperature) * 16.3)) - 38;
if (pixel < 0)
    pixel = 0;

Bitmap tmpCopy = new Bitmap(TemperatureIndicator.Width, TemperatureIndicator.Height);
tmpCopy.DrawImage(0, 0, TemperatureIndicator, 0, pixel, 34, 109);
display.SimpleGraphics.DisplayImage(tmpCopy, 137, 120);

There we have the user interface for a connected meteo station… if you look at connected object however, you will guess that most of them need some configuration (network, usernames…). The next part is about how to build a small webserver within the meteo station, with a small form to configure its parameters (timezone, etc).

Building a webserver and configuration interface within the meteo station

There’s a sample project for .NET Micro Framework that works great, but the .NET gadgeteer makes it even simpler: there’s a WebServer class already included, with basic routing functionalities. We’re going to use it. We will store the HTML files on the SD Card, as well as a configuration file.

Starting a WebServer is as simple as one line of code:

 WebServer.StartLocalServer(ethernet.NetworkSettings.IPAddress, 80);

To configure routing (i.e. associate code execution with specific URLs), use the WebEvent class:

 WebEvent webevt = WebServer.SetupWebEvent("Settings.htm");
webevt.WebEventReceived += new WebEvent.ReceivedWebEventHandler(webevt_WebEventReceived);

Once the webevent is configured, calling “https://ip:port/Settings.htm” will automatically trigger the execution of the handler associated with the string “Settings.htm”. What we need to do in the handler, is therefore to return, as an HTTP response, the content of the Settings.htm file.

 void webevt_WebEventReceived(string path, WebServer.HttpMethod method, Responder responder)
{
    responder.Respond(LoadHtmlFile(path), "text/html");
}

The Responder class makes it really easy to send an HTTP response, and takes a byte array as the first argument of its Respond method. This byte array is generated from an HTML file stored in the SD Card:

 private byte[] LoadHtmlFile(string path)
{ 
    byte[] result = null;
    var storage = sdCard.GetStorageDevice();
    using (var stream = storage.OpenRead(wwwRoot + path))
    {
        using (StreamReader sr = new StreamReader(stream))
        {
            string content = sr.ReadToEnd();
            result = new System.Text.UTF8Encoding().GetBytes(content);
        }
    }

    return result;
}

Once the form is submitted with the right parameters, it’s caught with another WebEvent, and the configuration is saved to the SD Card before sending the response:

 void setparams_WebEventReceived(string path, WebServer.HttpMethod method, Responder responder)
{
    Config c = new Config();
    c.Name = (string)responder.UrlParameters["Name"];
    c.UtcOffset = int.Parse(HttpUtility.UrlDecode((string)responder.UrlParameters["UtcOffset"]));

    SaveConfigToSdCard(c);

    responder.Respond(LoadHtmlFile(path), "text/html");
}

The HttpUtility class that I’m using is from the community and can be found here:

-
https://informatix.miloush.net/microframework/Source/UAM/InformatiX/SPOT/Web/HttpUtility.cs

 

The SaveConfigToSdCard method is very simple:

 private void SaveConfigToSdCard(Config c)
{
    var storage = sdCard.GetStorageDevice();
    using (var stream = storage.OpenWrite(configFilePath))
    {
        using (StreamWriter sw = new StreamWriter(stream))
        {
            sw.Write(c.ToString());
        }
    }
}

The Config object stores all the parameters :

 public class Config
{
    public string Name { get; set; }
    public int UtcOffset { get; set; }
        
    public static Config FromString(string str)
    {
        Config res = new Config();
        string[] settings = str.Split('\n');
        res.Name = settings[0].Split(':')[1];
        res.UtcOffset = int.Parse(settings[1].Split(':')[1]);
            
        return res;
    }

    public override string ToString()
    {
        return "name:" + Name + "\n utcoffset:" + UtcOffset.ToString();
    }
}

As you see, the serialization/deserialization mechanism is as simple as possible. Since we’re on an embedded system with limited capabilities, the simpler the code, the faster it runs: no need for fancy XML or JSON serialization here.

You could use the same kind of mechanism to build an API for your object so that other devices could connect directly to it. In the present case, it’s not necessary since we publish feeds to Cosm: we can use Cosm APIs!

As anything with a configuration, one has to think of a mechanism for resetting to factory defaults. We could do that with a simple button and a timer, it’s not really interesting to detail it here, but you’ll have to think about it!

Cherry on the cake: Using 3D modeling tool to prototype the object

There are two ways to build a “physical” object around an electronic kit such as the gadgeteer: either by trial and error, at the risk of using a lot more material than what’s needed, or by being modeling it either on paper or using 3D tools. The pen & paper is a simple approach so it works for simple objects, but the gadgeteer teams contributed 3D models of most parts of the kits, and even if you can’t afford professional tools such as SolidWorks (personally, I can’t) there are some free tools that work really great. While researching the subject to prepare this article I’ve used 123D, but I also gave a shot at FreeCad, SketchUp, and even had Blender installed (useful for 3D format transformation). These tools manipulate different formats, and for example SketchUp doesn’t support STEP files natively (this is the format used for the gadgeteer models). So you want to be careful about the tool you choose depending on the input format for models, and the output format you wish to have. Especially if you want to model some parts using a 3D printer!

Getting used to a CAD tool takes some time. You’re not going to make complex parts and assemblies overnight, and you’ll spend quite a few hours learning keyboard shortcuts, navigation, and good practices to be efficient. What I found out is that it’s more efficient to follow a couple of tutorials, rather than try to prototype directly the object you need. Then work on individual parts. Then assemble them. Don’t try to do the whole thing in one project directly. Here’s an example of the work in progress for the internals of my meteo station:

clip_image007[6]

It’s your call to see what method you prefer. For most of my projects I’ve been experimenting directly with hardware, but in this case, since I didn’t want to damage the case, and since I wanted to have a reuseable design, I decided to go with the CAD tools.

Conclusion

In this article, we’ve gone through the process of prototyping every function of a connected object, from the sensors, to the network, and configuration. Hopefully you now understand:

-
How to use the .NET Gadgeteer tooling

-
How to connect sensors, and get data from them

-
How to work with different kind of files on the SD Card

-
How to build a simple user interface

-
How to post them on the network

-
How to interface with Cosm

-
How to buld a webserver in your object

-
How to build a configuration mechanism

 

We also made use of a few toolkits (mftoolkit, HttpUtility, Pachube DAL, etc) that will hopefully help you, and concluded on the possibility to use CAD tools to physically design the object around the 3D models of the gadgeteer components. Now it’s your turn to build something!

Comments

  • Anonymous
    July 26, 2012
    Very nice article! I have shared it in TinyCLR forum: www.tinyclr.com/.../topic

  • Anonymous
    July 31, 2012
    Thanks a lot!