Tags

, , , ,

An example can go a long way, so rather than a 2 hour presentation with nothing but slides, I decided that the NxtGenUG Christmas special should include an end-to-end .NET Gadgeteer example. The example below includes the steps to build your own data logger to log both temperature and humidity. The coding examples were purposely kept short and do not include all the error checking that should be included, the aim was to show a wide variety of .NET Gadgeteer capabilities resulting in a final working prototype – within the 2 hour limit (including all coding and hardware building)!

[Note: I assume you have the latest SDK’s installed and that the firmware has been updated. I am using Visual Studio Ultimate, but the following will also work in the free version of VS.]

1) Lets start with a new project..

image

I have updated to the latest SDK/Drivers and firmware so we are currently targeting v4.2

image

2) There are currently a wide variety of sensors, actuators and displays available (over 130 different modules)- Lets start with a simple temperature sensor, this module uses the SHT10 temp and humidity sensor. To create a basic prototype using this we will need a mainboard ( GHI Spider) and a power module. This is achieved by dragging the modules from the toolbox on the right into the designer (main panel). By right clicking and selecting connect all modules you should end up with the following in the designer.

image

3) To begin with we will code the bare minimum so we can see the development cycle and get a feel for the environment. Notice that dragging modules on to the designer instantiates them, so they are ready to be used in your code. First we add some code to the MeasurementCompleted event and then put the sensor into continuous measurement mode. Each time the sensor acquires a new measurement our code will just print the readings out to the debug console. (Remember to turn this on if you are using VS Express – Debug-> Windows-> Output, See http://wp.me/p2pktY-3q). As you type VS will prompt you with intellisense and auto complete assistance, be sure to use these as it saves time.

  1. {        
  2.       void ProgramStarted()
  3.       {                       
  4.           Debug.Print("Program Started");
  5.           temperatureHumidity.MeasurementComplete += new TemperatureHumidity.MeasurementCompleteEventHandler(temperatureHumidity_MeasurementComplete);
  6.           temperatureHumidity.StartContinuousMeasurements();
  7.       }
  8.  
  9.       void temperatureHumidity_MeasurementComplete(TemperatureHumidity sender, double temperature, double relativeHumidity)
  10.       {
  11.           Debug.Print("Temp : " + temperature + " RH: " + relativeHumidity);
  12.       }

4) Next we will deploy the solution (hit F5/ Green arrow) this will compile the code and flash your mainboard with the solution. Note we are using the USB cable to power hardware as well as debug inside VS. Once the program has been deployed it will start and you can see the debug output in VS.

image

5) This is all very well but the Debug output is not a great place to view data, so let us add a display. There are a few to choose from but lets start with the T35 from GHI. Dragging and dropping the display in the designer and selecting connect all modules will result in the layout below.

image

6)I prefer to have a separate method to configure the display but feel free to have the code inside the program started method. We will need a font, one of the two supplied with the template. There are 2 modes of operation for the display, WPF and simple graphics. Many examples include the simple graphics option so we will use the WPF as a demo, suffice to say that this is a very basic interface and WPF is very powerful, so worth looking at further. The hardware display module has a WPFWindow property, this is the root of all the graphics that we will add. First we will add some text to the main canvas.

 

  1. Font baseFont;
  2. Window window;
  3. Canvas canvas = new Canvas();
  4. Text txtMsg;
  5.  
  6. private void setupWindow()
  7. {
  8.     baseFont = Resources.GetFont(Resources.FontResources.NinaB);
  9.     window = display_TE35.WPFWindow;
  10.     window.Child = canvas;
  11.     txtMsg = new Text(baseFont, "Starting…");
  12.     canvas.Children.Add(txtMsg);
  13. }

 

7) Each time the sensor returns a new reading we can use it to change the text content on the canvas. Note that we do not need to refresh the text, we are using the same text object.

  1. void temperatureHumidity_MeasurementComplete(TemperatureHumidity sender, double temperature, double relativeHumidity)
  2. {
  3.     Debug.Print("Temp : " + temperature + " RH: " + relativeHumidity);
  4.     txtMsg.TextContent =  "Temp : " + temperature + " RH: " + relativeHumidity;                        
  5. }

 

8) This text is a bit long, and has a few too many decimal places, so lets shorten the text.

  1. void temperatureHumidity_MeasurementComplete(TemperatureHumidity sender, double temperature, double relativeHumidity)
  2. {
  3.     Debug.Print("Temp : " + temperature + " RH: " + relativeHumidity);
  4.     txtMsg.TextContent = "Temp : " + temperature.ToString("F") + "           RH: " + relativeHumidity.ToString("F");                        
  5. }

 

9) The text is also rather close to the top of the screen, so we can nudge the canvas down a bit by adding a margin.

 

  1. canvas.SetMargin(5);

 

10) If all went well we are left with a user interface that updates, but is perhaps not beautiful.

image

 

11) Imagine that you are happy with this prototype and show it to friends/colleagues, who like the prototype but think the display is not very good and suggest adding a larger display. (Substituting hardware is something that is often required but can be very difficult to achieve, particularly when working with embedded systems). Lets go to the designer and deleted the T35 display and drag and drop the CP7 display into the solution. Connecting up the new display is the same as the old one and hitting build will result in an error. The error occurs because the display variable names are different (I just used the default names) – correcting this will let the solution compile and deploy.

image

12) With minimal effort the new display should look like the picture below. (Still not a fantastic interface) Remember that you need to build different prototype versions, experiment with hardware and test your ideas. The quicker you can develop new versions the quicker you can test you ideas and bring them into reality. 

picture000

 

13) The next step is to do something about this interface, it is lacking a bit of readability. The first step is to make the font bigger. This is not as simple as you imagine as there are just 2 default fonts in the template and they do not come in different sizes. Basically you would not want to waste all your precious embedded device memory on fonts! So you can add just the fonts (and character sets) that you need! There is a tool to import fonts called TFConvert which is command line driven (TFConvert more info: http://msdn.microsoft.com/en-us/library/cc533019.aspx). Like most things someone has written a tool to improve it :- http://informatix.miloush.net/microframework/Utilities/TinyFontTool.aspx

image

Once you have a new .tinyfnt file simply import is as a resource in VS (Add resource –> File-> *.tinyfnt) and it will become available in your code.

image

 

14) Lets just swap the font for the new font, intellisense will auto complete the FontResources, to show all the fonts you now have (should be 3).

 

  1. private void setupWindow()
  2. {
  3.     baseFont = Resources.GetFont(Resources.FontResources.SegoeUILarge);

 

15) Simply writing text to the display is a bit lazy, remember this is a WPFWindow, we have a canvas and a whole collection of WPF components that we could add. Most things in WPF end up in a stack panel of some sort, think of it as a bag that lets you throw things into and can display them either as a row or a column. I will add a stack panel to the canvas and then add the text for humidity and temperature as two separate text objects. Feel free to explore some of the methods at this point. For example you can catch touch events on WPF components such as a text object. I have added the complete code below as a reference.

 

  1. namespace TemperatureLogger
  2. {
  3.     public partial class Program
  4.     {
  5.         Font baseFont;
  6.         Window window;
  7.         Canvas canvas = new Canvas();
  8.         Text txtMsgTemp;
  9.         Text txtMsgRH;
  10.  
  11.  
  12.         private void setupWindow()
  13.         {
  14.             baseFont = Resources.GetFont(Resources.FontResources.SegoeUILarge);
  15.             window = display_CP7.WPFWindow;
  16.             window.Child = canvas;
  17.             txtMsgTemp = new Text(baseFont, "Starting…");
  18.             txtMsgRH = new Text(baseFont, "Starting…");
  19.             canvas.SetMargin(5);
  20.             txtMsgTemp.TextWrap = true;
  21.             StackPanel stack = new StackPanel();
  22.             stack.Children.Add(txtMsgTemp);
  23.             stack.Children.Add(txtMsgRH);
  24.             canvas.Children.Add(stack);
  25.              
  26.         }
  27.  
  28.         void ProgramStarted()
  29.         {
  30.             Debug.Print("Program Started");
  31.             setupWindow();
  32.  
  33.             temperatureHumidity.MeasurementComplete += new TemperatureHumidity.MeasurementCompleteEventHandler(temperatureHumidity_MeasurementComplete);
  34.             temperatureHumidity.StartContinuousMeasurements();
  35.         }
  36.  
  37.         void temperatureHumidity_MeasurementComplete(TemperatureHumidity sender, double temperature, double relativeHumidity)
  38.         {
  39.             Debug.Print("Temp : " + temperature + " RH: " + relativeHumidity);
  40.             txtMsgTemp.TextContent = "Temp : " + temperature.ToString("F");
  41.             txtMsgRH.TextContent = "RH: " + relativeHumidity.ToString("F");
  42.         }
  43.     }
  44. }

 

16) Text that updates is lovely but sometimes you just need to draw something! This is the only code that I will not manually type but it is short and simple enough to read through and understand. There is a basic graphing class that will draw grid lines and plot data points. Source code copied from from http://gadgeteering.net/sites/default/files/users/nvillar/files/SimpleGraph.cs . Code listed below for completeness.

 

  1. using System;
  2. using Microsoft.SPOT;
  3. using Microsoft.SPOT.Presentation;
  4. using Microsoft.SPOT.Presentation.Media;
  5. using Microsoft.SPOT.Presentation.Shapes;
  6. using Microsoft.SPOT.Presentation.Controls;
  7.  
  8. using GT = Gadgeteer;
  9.  
  10. namespace TemperatureLogger
  11. {
  12.     /// <summary>
  13.     /// This is a SimpleLine graph implemented as a WPF control.
  14.     /// </summary>
  15.     class SimpleGraph : Canvas
  16.     {
  17.         private int[] PointsX;
  18.         private int[] PointsY;
  19.  
  20.         Pen GraphPen;
  21.         private int PointIndex;
  22.         private double MinYValue;
  23.         private double MaxYValue;
  24.         private int DataPoints;
  25.  
  26.         private Pen GridPen;
  27.         private int VerticalGridSpacing = 0;
  28.         private int HorizontalGridSpacing = 0;
  29.         private bool ShowGrid = false;
  30.  
  31.         /// <summary>
  32.         ///
  33.         /// </summary>
  34.         /// <param name="height">Height of the graph, in pixels.</param>
  35.         /// <param name="width">Width of the graph, in pixels.</param>
  36.         /// <param name="minYValue">The smallest expected data value.</param>
  37.         /// <param name="maxYValue">The largest expected data value.</param>
  38.         /// <param name="dataPoints">The number of data points.</param>
  39.         /// <param name="lineThickness">The thickness of the graph line.</param>
  40.         /// <param name="lineColor">The color of the graph line.</param>
  41.         public SimpleGraph(uint height, uint width, double minYValue, double maxYValue, int dataPoints, ushort lineThickness, Color lineColor)
  42.         {
  43.             this.Width = (int)width + 1; // Extra pixels to accomodate a border (only visible when grid lines are on)
  44.             this.Height = (int)height + 1;
  45.             this.MinYValue = minYValue;
  46.             this.MaxYValue = maxYValue;
  47.             this.DataPoints = dataPoints;
  48.  
  49.             PointsX = new int[dataPoints];
  50.             PointsY = new int[dataPoints];
  51.  
  52.             GraphPen = new Pen(lineColor, lineThickness);
  53.  
  54.             Clear();
  55.         }
  56.  
  57.         /// <summary>
  58.         /// Plot a new value onto the graph. When there is no more room for new values, the graph will scroll.
  59.         /// </summary>
  60.         /// <param name="value"></param>
  61.         public void Plot(double value)
  62.         {
  63.             AddValue(value);
  64.  
  65.             this.Invalidate();
  66.         }
  67.  
  68.         /// <summary>
  69.         /// Clear the graph.
  70.         /// </summary>
  71.         public void Clear()
  72.         {
  73.             for (int i = 0; i < DataPoints; i++)
  74.             {
  75.                 PointsX[i] = i * (int)((double)this.Width / DataPoints);
  76.                 PointsY[i] = 0;
  77.             }
  78.  
  79.             PointIndex = 0;
  80.             this.Invalidate();
  81.         }
  82.  
  83.         /// <summary>
  84.         /// Display grid lines in the background of the graph.
  85.         /// </summary>
  86.         /// <param name="verticalLineSpacing">Spacing of vertical grid lines, in pixels.</param>
  87.         /// <param name="horizontalLineSpacing">Spacing of horizontal grid lines, in pixels.</param>
  88.         /// <param name="lineColor">Grid line color.</param>
  89.         public void DisplayGridLines(int verticalLineSpacing, int horizontalLineSpacing, GT.Color lineColor)
  90.         {
  91.             VerticalGridSpacing = verticalLineSpacing;
  92.             HorizontalGridSpacing = horizontalLineSpacing;
  93.  
  94.             GridPen = new Pen(lineColor, 1);
  95.             ShowGrid = true;
  96.         }
  97.  
  98.         public void HideGrid()
  99.         {
  100.             ShowGrid = false;
  101.         }
  102.  
  103.         public override void OnRender(DrawingContext dc)
  104.         {
  105.             base.OnRender(dc);
  106.  
  107.             if (ShowGrid)
  108.             {
  109.                 if (VerticalGridSpacing > 0)
  110.                 {
  111.                     // Draw vertical grid lines
  112.                     for (int x = 0; x < this.Width; x += VerticalGridSpacing)
  113.                     {
  114.                         dc.DrawLine(GridPen, x, 0, x, this.Height);
  115.                     }
  116.                 }
  117.  
  118.                 if (HorizontalGridSpacing > 0)
  119.                 {
  120.                     // Draw horizontal grid lines
  121.                     for (int y = 0; y < this.Height; y += HorizontalGridSpacing)
  122.                     {
  123.                         dc.DrawLine(GridPen, 0, y, this.Width, y);
  124.                     }
  125.                 }
  126.             }
  127.  
  128.             // Draw the graph lines
  129.             for (int i = 1; i < PointIndex; i++)
  130.             {
  131.                 dc.DrawLine(GraphPen, PointsX[i-1], PointsY[i-1], PointsX[i], PointsY[i]);
  132.                 dc.DrawLine(GraphPen, PointsX[i - 1], PointsY[i - 1] – 1, PointsX[i], PointsY[i] – 1);
  133.             }
  134.         }
  135.  
  136.         private void AddValue(double value)
  137.         {
  138.             int yPos = (int)(this.Height – (((double)(value – MinYValue) / (MaxYValue – MinYValue)) * this.Height));
  139.  
  140.             if (PointIndex < PointsY.Length)
  141.             {
  142.                 PointsY[PointIndex] = yPos;
  143.                 PointIndex++;  
  144.             }
  145.             else
  146.             {
  147.                 Array.Copy(PointsY, 1, PointsY, 0, DataPoints – 1);
  148.                 PointsY[DataPoints - 1] = yPos;
  149.             }
  150.         }
  151.  
  152.     }
  153. }

 

17) Import the simple graphing class and create a graph object.

  1. SimpleGraph tempGraph;

 

Select the colours, size and grid lines. Then add the graph object to the stack panel that we created earlier.

 

  1. tempGraph = new SimpleGraph(200, 300, 0,50, 300, 1, Colors.Blue);                    
  2. tempGraph.DisplayGridLines(60, 20, GT.Color.LightGray);
  3.  
  4. stack.Children.Add(tempGraph);

 

Then every time the sensor event triggers add the data to the graph.

 

  1. tempGraph.Plot(temperature);

 

The result should be a nice stack of text , text, graph! (Axis, units, colour, background, buttons are still required!) Hopefully that demonstrates how quite sophisticated GUIs can be generated using .NET Gadgeteer and very few lines of code.

WP_000674

18) Clearly we are making a sensor logger here, assuming the display now meets all you requirements, lets log the data to an SD card. First step is to add an SD module to the designer. (Drag, drop)

image 

 

19) First be sure to check you have an SD card, that it is formatted etc! We will just mount the storage that is in the SD module.

  1. //SDCard
  2. sdCard.MountSDCard();
  3. storage =  sdCard.GetStorageDevice(); //Do checks here

 

20) Every time the sensor gets some data, append it to the end of a file. Be sure to consider if this is a wise move, especially if you are polling the sensor frequently.  ( I will add a fs.flush() for the demo to show that data is written). There is also a storage FileWrite method that is worth considering, I did not use it here as I plan to flush the FileStream, to show that data ends up on the card without having to wait for a flush.

  1. byte[] data = Encoding.UTF8.GetBytes(msg + "\n");
  2. using (FileStream fs = storage.Open("data.log", FileMode.Append, FileAccess.Write))
  3. {
  4.     fs.Write(data, 0, data.Length);
  5. }

21) We have a data display, and logger, but nothing is complete without some sort of internet capability. Lets make our sensor available across the internet! .NET Gadgeteer easily lets you set up a web server. Lets add an Ethernet connection in the designer:

 image

You could just as easily add a WiFi module:

image

Note: If you use a WiFi module here is a handy way to setup an adhoc network on your Win8 laptop/PC

Setup wireless network (from http://www.addictivetips.com/windows-tips/how-to-create-wireless-ad-hoc-internet-connection-in-windows-8/)

> netsh wlan set hostednetwork mode=allow ssid=MyNetwork key=MyNetwork

> netsh wlan start hostednetwork

(Be sure to run as admin)

image

Note: Here is a good webserver example http://mikedodaro.net/2011/10/15/gadgeteer-web-service-surveillance-camera/

 

22) Be sure to add more error checking than is shown here,  to your networking capabilities.

 

23) Lets start a webserver when we get an IP address. First register to get notifications when the IP changes, enable DHCP and then open the network.

  1. ethernet_J11D.Interface.NetworkAddressChanged += new NetworkInterfaceExtension.NetworkAddressChangedEventHandler(Interface_NetworkAddressChanged);
  2. ethernet_J11D.Interface.NetworkInterface.EnableDhcp();
  3. ethernet_J11D.Interface.Open();

As an aside I noticed that it can take the WiFi module a while to start-up so I added the initialisation into a delayed timer.

  1. //WiFi connect
  2. GT.Timer wifiTmr = new GT.Timer(15000);
  3. wifiTmr.Tick += new GT.Timer.TickEventHandler(wifiTmr_Tick);
  4. wifiTmr.Start();

 

24) Again it is wise to add some sanity checks here, but we will assume that our networking is perfect , that we get an IP address and simply start a webserver on port 80. The great thing about the webserver is that you can setup events on the url, here we add an event that will be triggered when the url http://<IP>/Temp is requested.  (Simple eh!)

 

  1. void Interface_NetworkAddressChanged(object sender, EventArgs e)
  2. {
  3.    WebServer.StartLocalServer(ethernet_J11D.Interface.NetworkInterface.IPAddress, 80);
  4.    WebEvent we = WebServer.SetupWebEvent("Temp");
  5.    we.WebEventReceived += new WebEvent.ReceivedWebEventHandler(we_WebEventReceived);
  6. }

 

It is always polite to return a response,  we can simply return a webpage (basic!)

  1. void we_WebEventReceived(string path, WebServer.HttpMethod method, Responder responder)
  2. {
  3.     
  4.     responder.Respond("<html><body> Temperature : "+ txtMsgTemp.TextContent+ "</body></html>");
  5. }

 

Summary

In just 2 hours we have built an example data logger, with a graphical frontend, storage capabilities as well as making the datalogger internet capable! Aside from the SimpleGraph capabilities we have assembled all the hardware and typed all the code to make the prototype work, including importing a custom font. Hopefully this demonstrates the power of .NET Gadgeteer for rapid prototype creations! A few shortcuts were taken and it is highly recommended to add some error checking.

Note: Throughout this presentation I have purposely iterated through the development cycle to simulate the process you will encounter developing a prototype. Each change resulted in a build, compile and deploy (F5 in VS) and then a simple test to show the expected output. We will have also experienced the debug breakpoint feature which is very powerful. It is important to realise that we can halt execution on the actual hardware and inspect the objects that are in scope, this is probably one of the most valuable debug features and is a fantastic capability for embedded devices!