📜 ⬆️ ⬇️

IoT example: Making a bitcoin monitor from a Nokia screen, a Netduino board and a cloud

My intern and I were asked to speak at Durban University of Technology to third-year students in order to inspire them with the possibilities of using Netduino and / or Windows Phone in their projects.

We wanted to show not just a flashing LED, but something that would be relevant to real live scenarios. And we decided to demonstrate this:


')
Sorry for the terrible gif. You can call it a bitcoin price tracker. The graph reflects the change, and the LED changes to green when the price rises and red when the price drops. (You may find a typo on the screen - instead of USD, BTC should be displayed).

WP_20140213_10_14_46_Pro - Copy

Azure


The first obvious step for us is to determine where the data will come from. One of the simple and easy-to-use APIs comes from mtgox (note: due to various kind of conflicting discussions around them, it may make sense to use something like Bitstamp). The problem is that, along with the price data we need, the source produces a lot of other data that would be too expensive to load and process on Netduino. The solution is to create an intermediate service between the device and the API, which cuts out unnecessary data. And doing it with Azure is REALLY easy.

Create a new website on the Azure portal:

image

Specify any URL as desired and start the creation of the site, then going to Visual Studio. Create a new ASP.NET MVC web application:

image

We are going to download the latest price data from the mtgox API and send abbreviated price data. To do this, we need to deserialize JSON data into .NET objects. Access the API and copy the JSON result. Then create a new .cs file in the Models folder called MtGoxTicker.cs . Remove the default class definition and paste the copied text using the Edit> Paste Special> Paste JSON as classes command. A whole pack of classes that displays the structure of the API will be created automatically. Rename the RootObject to MtGoxTicker.

Hidden text
public class MtGoxTicker { public string result { get; set; } public Data data { get; set; } } public class Data { public High high { get; set; } public Low low { get; set; } public Avg avg { get; set; } public Vwap vwap { get; set; } public Vol vol { get; set; } public Last_Local last_local { get; set; } public Last_Orig last_orig { get; set; } public Last_All last_all { get; set; } public Last last { get; set; } public Buy buy { get; set; } public Sell sell { get; set; } public string item { get; set; } public string now { get; set; } } public class High { public string value { get; set; } public string value_int { get; set; } public string display { get; set; } public string display_short { get; set; } public string currency { get; set; } } public class Low { public string value { get; set; } public string value_int { get; set; } public string display { get; set; } public string display_short { get; set; } public string currency { get; set; } } public class Avg { public string value { get; set; } public string value_int { get; set; } public string display { get; set; } public string display_short { get; set; } public string currency { get; set; } } public class Vwap { public string value { get; set; } public string value_int { get; set; } public string display { get; set; } public string display_short { get; set; } public string currency { get; set; } } public class Vol { public string value { get; set; } public string value_int { get; set; } public string display { get; set; } public string display_short { get; set; } public string currency { get; set; } } public class Last_Local { public string value { get; set; } public string value_int { get; set; } public string display { get; set; } public string display_short { get; set; } public string currency { get; set; } } public class Last_Orig { public string value { get; set; } public string value_int { get; set; } public string display { get; set; } public string display_short { get; set; } public string currency { get; set; } } public class Last_All { public string value { get; set; } public string value_int { get; set; } public string display { get; set; } public string display_short { get; set; } public string currency { get; set; } } public class Last { public string value { get; set; } public string value_int { get; set; } public string display { get; set; } public string display_short { get; set; } public string currency { get; set; } } public class Buy { public string value { get; set; } public string value_int { get; set; } public string display { get; set; } public string display_short { get; set; } public string currency { get; set; } } public class Sell { public string value { get; set; } public string value_int { get; set; } public string display { get; set; } public string display_short { get; set; } public string currency { get; set; } } 

In the Controllers folder, create a new WebAPI controller or use one of the existing ones. You can remove all methods except Get. All that is required of this method is to load the latest price data from the API and return the single BUY value we need.

Change the return data type Get to double, then bring the method to the following form:

Hidden text
  public double Get() { try { var wc = new WebClient(); var response = JsonConvert.DeserializeObject<MtGoxTicker>(wc.DownloadString("https://data.mtgox.com/api/2/BTCUSD/money/ticker")); if (response != null) { return double.Parse(response.data.buy.value); } } catch (Exception) { } return -1; } 

Simply put, when someone calls the Get method, it loads prices from the API, deserializes them and returns only the data we need.

The last thing we need to do is make our API always return a response in JSON. Go to the last line of the Appication_Start method in Global.asax.cs and add the following code after the existing lines:

 GlobalConfiguration.Configuration.Formatters.Clear(); GlobalConfiguration.Configuration.Formatters.Add(new JsonMediaTypeFormatter()); 

At this point, your website in Azure should have already been created, so we can move on and place the site directly from Visual Studio. The easiest way to do this is to right-click on the project in Solution Explorer and select the Publsh command. Then click Import and go through the process of login and import of the details of your Azure subscription (this process looks much nicer in Visual Studio 2013). Publishing your site may take a minute or two, after which you can go to it.

In order to test how your site should look, you can go to my http://bitcoinpusher.azurewebsites.net/api/Price (note: I cannot guarantee that this link will work forever).

Hardware


- Netduino Plus (or Plus 2)



- Nokia 5110 LCD ($ 4 on the DX portal)

- RGB LED - we used SMD 5050, but any RGB LED will do.




Netduino


Netduino will upload the latest price data from the service to Azure, display it and draw a graph. This process will be repeated periodically in an infinite loop and will update the data as quickly as it can (will depend on the speed of the Internet).

To write on the screen of the Nokia 5110 LCD, we will use a library developed by a member of the Netduino community, about which you can read about this link . Keep in mind that there is an error in the line drawing algorithm, so you may need to download the source code (at the end of this article) in order to use my revised version.

To simplify network calls, we can use an HTTP client from the .NET MF Toolbox .

Instead of describing the code line by line, I publish all the code in Program.cs with comments. It is simple enough to understand:

Hidden text
  public class Program { //the red and green pins of the RGB LED private static OutputPort _redPin = new OutputPort(Pins.GPIO_PIN_D1, false); private static OutputPort _greenPin = new OutputPort(Pins.GPIO_PIN_D0, false); public static void Main() { //setup the LCD with appropriate pins. var lcd = new Nokia_5110(true, Pins.GPIO_PIN_D10, Pins.GPIO_PIN_D9, Pins.GPIO_PIN_D7, Pins.GPIO_PIN_D8) { BacklightBrightness = 100 }; //create these to store values var history = new Queue(); double lastValue = 0; var nextUpdateTime = DateTime.MinValue; while (true) { try { //download the price from our API var WebSession = new HTTP_Client(new IntegratedSocket("bitcoinpusher.azurewebsites.net", 80)); var response = WebSession.Get("/api/price/"); if (response.ResponseCode == 200) { //convert the price to a double from a string var result = double.Parse(response.ResponseBody); //if the value went up, change the LED to green, if it went down change to red if (result > lastValue) { _greenPin.Write(true); _redPin.Write(false); } else if (result < lastValue) { _greenPin.Write(false); _redPin.Write(true); } //store this value so we can compare it to the next one lastValue = result; //only add points to the graph every x seconds, else it will barely move if (DateTime.Now > nextUpdateTime) { history.Enqueue(result); //store a max of 80 data points as each point will take up 1 pixel, and the screen is //only 80 wide if (history.Count > 80) { history.Dequeue(); } //store a value of what time we should add the next data point to the list nextUpdateTime = DateTime.Now.AddSeconds(15); } var high = 0d; var low = double.MaxValue; //find the max and min value to determine our range (for the graph). //The reason for this is so that the min value will be the very bottom of the graph, and //the max value will be the very top of the graph regardless of what the values are foreach (double item in history) { if (item < low) { low = item; } if (item > high) { high = item; } } if (high == low) { //if all numbers are the same, artificially seperate high and low so that the //graph will draw in the middle of the screen. Without doing this the //point will be at the very top. high--; low++; } double diff = high - low; lcd.Clear(); short x = 1; short prevY = -1; //this loop draws a line from the previous point to the current point, which makes the graph foreach (double item in history) { //work out the y value based on the min/max range, and the available height. //We have 39 pixels height to work with, and shift it by 9 at the end so it doesn't //overlap the text at the top var thisY = (short)((39 - (((item - low) / diff) * 39)) + 9); if (prevY != -1) //don't draw from 0,0 { //draw a line from the previous point to this point lcd.DrawLine((short)(x - 1), prevY, x, thisY, true); } //remember this pos so we can draw a line from it in the next iteration prevY = thisY; x++; } //Refresh pushes all DrawLine/Rect/Point calls to the screen //Note that this does not apply to writing text, which pushes instantly lcd.Refresh(); //there is no Math.Round(double,int) so use ToString to get 4 decimals lcd.WriteText("$ " + result.ToString("f4") + " / BTC"); } else { FlickerLEDForFaliure(); } } catch (Exception) { FlickerLEDForFaliure(); } } } private static void FlickerLEDForFaliure() { //if the downlaod of the new value fails then flicker the LED. After flickering turn the LED off for (int i = 0; i < 30; i++) { _redPin.Write(true); Thread.Sleep(100); _greenPin.Write(true); Thread.Sleep(100); _redPin.Write(false); Thread.Sleep(100); _greenPin.Write(false); } } } 

The following is the connection diagram:

BitcoinPusher_bb

WP_20140128_16_08_32_Pro - Copy

I left this circuit connected for the night and it worked fine. Disconnection from the network and reverse connection is also processed normally.

Windows phone


A Windows Phone application does exactly the same thing as Netduino itself: it loads the price, displays it, and then draws a graph.

The UI is just a TextBlock for displaying the price and a Grid that will contain lines.

wp_ss_20140202_0001

Hidden text
  <Grid x:Name="LayoutRoot" Background="Transparent"> <Grid.RowDefinitions> <RowDefinition Height="Auto"/> <RowDefinition Height="*"/> </Grid.RowDefinitions> <StackPanel x:Name="TitlePanel" Grid.Row="0" Margin="12,17,0,28"> <TextBlock Text="price" x:Name="PriceTextBlock" Margin="9,-7,0,0" Style="{StaticResource PhoneTextTitle2Style}" FontSize="40"/> </StackPanel> <Grid x:Name="ContentPanel" Grid.Row="1" Margin="0"> </Grid> </Grid> 

Below you will find the code. Please note that since the code is completely based on the code for Netduino, I did not comment on it in detail. Refer to the code for Netduino, if you do not understand something.

Hidden text
  public partial class MainPage : PhoneApplicationPage { private Queue<double> _history = new Queue<double>(); private double _lastValue = 0; private DateTime _nextUpdateTime = DateTime.MinValue; public MainPage() { InitializeComponent(); DownloadAndPlotPrice(); } private async void DownloadAndPlotPrice() { try { var WebSession = new HttpClient(); var response = await WebSession.GetAsync("http://bitcoinpusher.azurewebsites.net/api/price/"); if (response.IsSuccessStatusCode) { var result = double.Parse(await response.Content.ReadAsStringAsync()); //if the value went up, change the back to green, if it went down change to red if (result > _lastValue) { LayoutRoot.Background = new SolidColorBrush(Colors.Green); } else if (result < _lastValue) { LayoutRoot.Background = new SolidColorBrush(Colors.Red); } _lastValue = result; //only add points to the graph every x seconds, else it will barely move if (DateTime.Now > _nextUpdateTime) { _history.Enqueue(result); if (_history.Count > 400) { _history.Dequeue(); } _nextUpdateTime = DateTime.Now.AddSeconds(4); } var high = 0d; var low = double.MaxValue; //find the max and min value to determine our range (for the graph) foreach (double item in _history) { if (item < low) { low = item; } if (item > high) { high = item; } } if (high == low) { //if all numbers are the same, artificially seperate high and low so that the //graph will draw in the middle of the screen high--; low++; } double diff = high - low; //remove all previous lines in preperation for redrawing them ContentPanel.Children.Clear(); short x = 1; short prevY = -1; foreach (double item in _history) { //we now have 300 pixels of vertical space to play with var thisY = (short)(300 - (((item - low) / diff) * 300)); if (prevY != -1) //don't draw from 0,0 { //draw a line from the previous point to this point //Line is a XAML control that we use to display lines ContentPanel.Children.Add(new Line { X1 = (x - 1), Y1 = prevY, X2 = x, Y2 = thisY, StrokeThickness = 4, Stroke = new SolidColorBrush(Colors.White) }); } prevY = thisY; x++; } PriceTextBlock.Text = ("$ " + result.ToString("f5") + " / BTC"); } else { ShowFaliureFaliure(); } } catch (Exception) { ShowFaliureFaliure(); } DownloadAndPlotPrice(); } private async void ShowFaliureFaliure() { //if the download of the new value fails then flicker the background. for (int i = 0; i < 30; i++) { LayoutRoot.Background = new SolidColorBrush(Colors.Orange); await Task.Delay(100); LayoutRoot.Background = new SolidColorBrush(Colors.Black); await Task.Delay(100); } } } 

That's all. Let me know if you have questions or problems in the comments (on the original article - approx. Transl.) Or on Twitter .

You can download the source files from here !


Additional links


Source: https://habr.com/ru/post/228285/


All Articles