Приклад IoT: Робимо bitcoin-монітор з екрана від Nokia, плати від Netduino і хмари

      Мого інтерна і мене попросили виступити в Дурбанську технологічному університеті перед студентами третього курсу для того щоб надихнути їх можливостями використання Netduino і / або Windows Phone в їх проектах.
 
Нам хотілося показати не просто миготливий світлодіод, але що-небудь що буде мати відношення до реальних живим сценаріями. І ми вирішили продемонструвати це:
 
 
 
Просимо вибачення за жахливий GIF. Ви можете назвати це трекером зміни ціни на біткоіни. Граф відображає зміну, а світлодіод змінює колір на зелений при зростанні ціни і на червоний, коли ціна падає. (На екрані ви можете виявити друкарську помилку — замість USD має виводитися BTC).
 
 WP_20140213_10_14_46_Pro - Copy
 
 Azure
Першим очевидним кроком для нас є визначення місця звідки будуть надходити дані. Один з простих і легких для використання API поставляється mtgox (примітка: у зв'язку з різними роду суперечливими дискусіями навколо них, може мати сенс використовувати щось на зразок Bitstamp). Проблема в тому, що разом з потрібними нам даними про ціну, джерело видає безліч інших даних, які буде занадто витратно завантажувати і обробляти на Netduino. Рішення полягає в тому щоб створити проміжний сервіс між пристроєм і API, який вирізає непотрібні дані. І зробити це за допомогою Azure ДІЙСНО легко.
 
Створимо новий веб-сайт на порталі Azure:
 
 image
 
Вкажіть будь URL за бажанням і запустіть створення сайту, перейшовши потім в Visual Studio. Створіть нове веб-додаток ASP.NET MVC:
 
 image
 
Ми збираємося завантажити останні дані про ціну з mtgox API і відправляти скорочені дані про ціни. Для цього нам необхідно десеріалізовать JSON-дані в. NET-об'єкти. Зверніться до API і скопіюйте JSON-результат. Потім створіть новий. Cs-файл в папці Models під назвою MtGoxTicker.cs . Приберіть визначення класу за замовчуванням і за допомогою команди Edit> Paste Special> Paste JSON as classes вставте скопійований текст. Ціла пачка класів, яка відображає структуру API, буде створена автоматично. Перейменуйте об'єкт RootObject в MtGoxTicker.
 
 Прихований текст
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; }
    }

У папці Controllers створіть новий WebAPI-контролер або використовуйте один з існуючих. Ви можете видалити всі методи, крім Get. Все що потрібно від цього методу — це завантажити останні дані про ціни з API і повернути одне значення BUY, яке нам потрібно.
 
Змініть тип повертаються даних Get на double, потім приведіть метод в наступний вигляд:
 
 Прихований текст
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;
        }

Простіше кажучи, коли хтось викликає метод Get, він завантажує ціни з API, десеріалізует їх і повертає тільки необхідні нам дані.
 
Останнє що нам потрібно зробити — це змусити наш API завжди повертати відповідь в JSON. Перейдіть до останній сходинці методу Appication_Start в Global.asax.cs і додайте наступний код після існуючих рядків:
 
 
GlobalConfiguration.Configuration.Formatters.Clear();
GlobalConfiguration.Configuration.Formatters.Add(new JsonMediaTypeFormatter());

До цього моменту ваш веб-сайт в Azure вже повинен був створитися, так що ми можемо рухатися далі і розмістити сайт прямо з Visual Studio. Найлегший спосіб зробити це — натиснути правою кнопкою на проекті в Solution Explorer і вибрати команду Publsh. Потім натисніть Import і пройдіть процес логіна і імопрта деталей вашої Azure-підписки (цей процес виглядає значно приємніше в Visual Studio 2013). Публікація вашого сайту може зайняти хвилину чи дві, після чого ви зможете перейти на нього.
 
Для того щоб протестувати то, як повинен виглядати ваш сайт, ви можете перейти на мій http://bitcoinpusher.azurewebsites.net/api/Price (примітка: я не можу гарантувати, що це посилання буде працювати вічно).
 
 Hardware
 - Netduino Plus (або Plus 2)
 
 
 
 - Nokia 5110 LCD ($ 4 на порталі DX)
 
 - RGB LED — ми використовували SMD 5050, але підійде будь RGB LED.
 
 
 
 
 Netduino
Netduino буде завантажувати останні дані про ціну з сервісу в Azure, відображати їх і малювати графік. Цей процес буде повторюватися періодично в нескінченному циклі і буде оновлювати дані так швидко, як зможе (залежатиме від швидкості інтернету).
 
Щоб писати на екран Nokia 5110 LCD ми будемо використовувати бібліотеку, розроблену членом спільноти Netduino, про яку ви можете прочитати за цим посиланням . Майте на увазі, що в алгоритмі малювання лінії є помилка, так що вам може знадобитися завантажити вихідні коди (наприкінці цієї статті) для того щоб використовувати мою виправлену версію.
 
Для спрощення мережевих дзвінків ми можемо використовувати HTTP-клієнт з інструментів . NET MF Toolbox .
 
Замість опису коду рядок за рядком, я публікую весь код Program.cs з коментарями. Він досить простий для розуміння:
 
 Прихований текст
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);
            }
        }
 
    }

Нижче представлена ​​схема сполук:
 
 BitcoinPusher_bb
 
 WP_20140128_16_08_32_Pro - Copy
 
Я залишив підключеної цю схему на ніч і вона відпрацювала нормально. Від'єднання від мережі і зворотне підключення теж обробляється нормально.
 
 Windows Phone
Додаток Windows Phone робить рівно те ж саме що і сам Netduino: завантажує ціну, відображає її і потім малює графік.
 
UI — це просто TextBlock для відображення ціни і Grid, який міститиме лінії.
 
 wp_ss_20140202_0001
 
 Прихований текст
<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>

Нижче ви знайдете код. Зверніть увагу, що так як код повністю заснований на коді для Netduino, я не став його докладно коментувати. Зверніться до коду для Netduino, якщо вам щось не зрозуміло.
 
 Прихований текст
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);
            }
        }
    }

На цьому все. Дайте мені знати, якщо у вас будуть питання або проблеми в коментарях (оригінальної статті — прим. Перекл.) Або на твіттері .
 
 Ви можете завантажити вихідні файли звідси !
 Додаткові посилання
 

  
Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.