Windows 10 IoT Core, Raspberry PI, Arduino and enuSpace Platform


IoT 통합 플랫폼 연계 작업 내역 (enuSpace for mars - ver 2.0)

Raspberry PI에 윈도우즈 10 IoT Core를 설치하고 Arduino의 센서 Analog 신호를 시리얼 통신으로 입력 받아 enuSpace Platform에 http 통신을 이용하여 데이터를 받아서 처리하는 일련의 작업과정을 설명합니다.

아래 동영상은 enuSpace 통합 플랫폼 웹서버와 라즈베리 파이에 Windows 10 IoT Core App간의 연동 내용을 포함하고 있습니다.


Step1. Windows 10 iot core 설치

https://developer.microsoft.com/ko-kr/windows/iot/GetStarted

위 링크의 내용을 기반으로 단계별로 설치 절차를 진행합니다.  

Dashboad를 다운 받아 프로그램을 설치합니다.

이미지를 다운 받아서 Flash Memory에 이미지를 저장합니다.

Flash Memory를 라으베리 파이에 삽입하고 전원을 연결하면 모든 설치가 완료됩니다.

설치가 끝난후 개발자 모드 설정을 수행합니다.


윈도우 Edge 또는 웹브라우져를 이용하여 Window 10 iot core가 설치된 Raspberry PI에 접속을 수행합니다.

Windows 10 IoT Core의 기본 App의 IoTCoreDefaultApp 실행 화면입니다.


아래 그림과 같이 Raspberry PI 3와 Arduino 디바이스간에 USB Serial을 통하여 연결을 수행하고 아두이노에 센서 2종을 연결한 화면입니다.

라즈베리 파일에 Windows 10 IoT Core 를 설치, 아두이노를 연결 및 센서 2종 음향 및 플래어 센서 연결을 마무리 하였다면 아두이노 프로그램밍을 수행합니다.


Step 2. 아두이노 프로그래밍

Arduino를 USB로 연결하고 Arduino IDE에서 예제 AnalogOutSerial을 참조하여 코드를 수정하여 컴파일 및 배포를 수행합니다.


const int analogInPin1 = A0;  

const int analogInPin2 = A1; 

int sensorValue = 0;       

int sensorValue2 = 0;       

String Buffer ;

void setup() {

  Serial.begin(250000);

}

 

void loop() {

  // read the analog in value:

  sensorValue = analogRead(analogInPin1);

sensorValue2 = analogRead(analogInPin2);

 

  // print the results to the serial monitor:

  Buffer = "";

  Buffer += sensorValue;

  Buffer += ":";

  Buffer += sensorValue2;

Serial.println(Buffer);

}


Step 3. 라즈베리 파이(Raspberry PI)에 WinRT 응용프로그램을 개발(WinRT C#) 절차

Microsoft Visual Studio 2015 다운로드 사이트

https://developer.microsoft.com/ko-kr/windows/downloads

Microsoft Visual Studio 2015를 이용하여 IoT C# 프로젝트를 생성합니다.


아래 사이트에 가시면 시리얼 통신에 적합한 샘플을 구할수 있습니다.

https://developer.microsoft.com/en-us/windows/iot/samples/serialuart

위 사이트에서 샘플을 다운 받아서 간단하게 C#용으로 시리얼 통신을 수행합니다.

시리얼 통신으로 부터 전달받은 데이터를 차트에 표현하도록 하겠습니다. 

차트를 드로잉하기 위해서 nuget에 있는 https://www.nuget.org/packages/WinRTXamlToolkit.Controls.DataVisualization.UWP/

Toolkit을 이용하는 방법이 있습니다.  

Window 10 iot core 용으로 다운 받을 경우 UWP 버젼으로 다운 받아야 한다. 정상적으로 받았을 경우 xaml 파일과 cs 소스 코드를 이용하여 개발을 수행합니다. 

        <WinRT_Charting:Chart x:Name="LineChart1" Title="My Chart">

            <WinRT_Charting:LineSeries Title="T1"

                        ItemsSource="{Binding Items}"

                        IndependentValueBinding="{Binding Name}"

                        DependentValueBinding="{Binding Value}"

                        IsSelectionEnabled="True"/>

            <WinRT_Charting:LineSeries Title="T2"

                        ItemsSource="{Binding Items}"

                        IndependentValueBinding="{Binding Name}"

                        DependentValueBinding="{Binding Value}"

                        IsSelectionEnabled="True"/>

        </WinRT_Charting:Chart>


private void updateChart_Click(object sender, RoutedEventArgs e)

        {

            try

            {

                List<NameValueItem> newitems1 = new List<NameValueItem>();

                List<NameValueItem> newitems2 = new List<NameValueItem>();

 

                int num = items1.Count / 250;

                int con = 0;

                int value1 = 0;

                int value2 = 0;

                string name;

                for (var i = 0; i < items1.Count; i++)

                {

                     name = Convert.ToString(newitems1.Count);

                     newitems1.Add(new NameValueItem { Name = name, Value = items1[i].Value });

                     newitems2.Add(new NameValueItem { Name = name, Value = items2[i].Value });


                }

                ((LineSeries)this.LineChart1.Series[0]).ItemsSource = newitems1;

                ((LineSeries)this.LineChart1.Series[1]).ItemsSource = newitems2;

 

                newitems1 = null;

                newitems2 = null;

            }

            catch (Exception ex)

            {

                status.Text = ex.Message;

            }

        } 

센서값을 차트로 표현결과, 라즈베리 파이에서 그래픽 리소스를 많이 잡아먹는 WinRTXamlToolkit.Controls.DataVisualization를 이용할 경우에는 많은 양의 데이터를 표현은 어렵다는것을 확인하였습니다. 약 1000개의 데이터를 현시할 경우, 약 30초정도 소요됩니다. 아래 그림은 1000개의 데이터를 차트에 표현하여 보았습니다. 

실시간으로 데이터 표현이 어렵기 때문에, 데이터를 취득하는 버튼과 데이터 취득 종료버튼을 별도로 이용하여 데이터를 분석하는 모듈로 구성하였습니다. 

만약 실시간으로 차트를 표현하고자 한다면, Canvas를 이용하여 직접 Drawing을 수행하면 빠른 현시가 가능합니다. 


Canvas객체를 이용하 WinRT C# Chart 드로잉

private void DrawChart(Canvas canGraph, List<NameValueItem>[] pList, double axisx_min, double axisx_max, double data_min, double data_max)

        {

            canGraph.Children.Clear();


            double[] RectWall;

            RectWall = new double[4];

            RectWall[0] = 0;                // left 

            RectWall[1] = 0;                // top

            RectWall[2] = canGraph.ActualWidth;   // right

            RectWall[3] = canGraph.ActualHeight;  // bottom


            double[] RectGap;

            RectGap = new double[4];

            RectGap[0] = 30;    // left 

            RectGap[1] = 20;    // top

            RectGap[2] = 20;    // right

            RectGap[3] = 20;    // bottom


            double[] RectChart;

            RectChart = new double[4];

            RectChart[0] = RectWall[0] + RectGap[0]; // left 

            RectChart[1] = RectWall[1] + RectGap[1]; // top

            RectChart[2] = RectWall[2] - RectGap[2]; // right

            RectChart[3] = RectWall[3] - RectGap[3]; // bottom


            double fChartWidth = RectChart[2] - RectChart[0];

            double fChartHeight = RectChart[3] - RectChart[1];


            int xGridNum = 5;

            int yGridNum = 10;

            double fGridGapX = fChartWidth / xGridNum;

            double fGridGapY = fChartHeight / yGridNum;


            // 차트 그리기.

            Rectangle pChartRect = new Rectangle();

            pChartRect.StrokeThickness = 1;

            pChartRect.Fill = new SolidColorBrush(Colors.Black);

            pChartRect.Stroke = new SolidColorBrush(Colors.Yellow);

            pChartRect.Width = fChartWidth;

            pChartRect.Height = fChartHeight;

            Canvas.SetLeft(pChartRect, RectChart[0]);

            Canvas.SetTop(pChartRect, RectChart[1]);

            canGraph.Children.Add(pChartRect);


            // x축 그리기.

            int iCount = 0;

            GeometryGroup xaxis_geom = new GeometryGroup();

            for (double x = 0; x <= fChartWidth; x += fGridGapX)

            {

                LineGeometry xtick = new LineGeometry();

                xtick.StartPoint = new Point(x + RectChart[0], RectChart[3] + 5);

                xtick.EndPoint = new Point(x + RectChart[0], RectChart[1]);

                xaxis_geom.Children.Add(xtick);


                TextBlock xlabel = new TextBlock();

                int ivalue = (int)axisx_min + (int)(axisx_max - axisx_min) / xGridNum * iCount;

                xlabel.Text = ivalue.ToString();

                xlabel.FontSize = 10;

                Canvas.SetLeft(xlabel, x + RectChart[0]);

                Canvas.SetTop(xlabel, RectChart[3] + 5);

                canGraph.Children.Add(xlabel);

                iCount = iCount + 1;

            }


            Path xaxis_path = new Path();

            xaxis_path.StrokeThickness = 1;

            xaxis_path.Stroke = new SolidColorBrush(Colors.Green);

            xaxis_path.Data = xaxis_geom;

            canGraph.Children.Add(xaxis_path);


            // y축 그리기.

            iCount = 0;

            GeometryGroup yxaxis_geom = new GeometryGroup();

            for (double y = 0; y <= fChartHeight; y += fGridGapY)

            {

                LineGeometry ytick = new LineGeometry();

                ytick.StartPoint = new Point(RectChart[0] - 5, RectChart[3] - y);

                ytick.EndPoint = new Point(RectChart[2], RectChart[3] - y);

                yxaxis_geom.Children.Add(ytick);


                TextBlock ylabel = new TextBlock();

                int ivalue = (int)data_min + (int)(data_max - data_min) / yGridNum * iCount;

                ylabel.Text = ivalue.ToString();

                ylabel.FontSize = 10;

                Canvas.SetLeft(ylabel, RectChart[0] - 20);

                Canvas.SetTop(ylabel, RectChart[3] - y - ylabel.FontSize);

                canGraph.Children.Add(ylabel);

                iCount = iCount + 1;

            }


            Path yaxis_path = new Path();

            yaxis_path.StrokeThickness = 1;

            yaxis_path.Stroke = new SolidColorBrush(Colors.Green);

            yaxis_path.Data = yxaxis_geom;

            canGraph.Children.Add(yaxis_path);


            // data 그리기.

            double x1 = 0;

            double y1 = 0;

            double x2 = 0;

            double y2 = 0;

            

            int idim = 0;

            idim = pList.Length;

            if (idim > 0)

            {

                for (int i = 0; i < idim; i++)

                {

                    GeometryGroup data_geom = new GeometryGroup();


                    List<NameValueItem> newitems = pList[i];

                    double xstep = fChartWidth / newitems.Count;


                    for (int j = 0; j < newitems.Count-1; j++)

                    {

                        LineGeometry vline = new LineGeometry();


                        x1 = RectChart[0] + xstep * j;

                        y1 = RectChart[3] - fChartHeight * ((newitems[j].Value- data_min) / (data_max - data_min));

                        if (y1 < RectChart[1])

                            y1 = RectChart[1];

                        if (y1 > RectChart[3])

                            y1 = RectChart[3];

                        x2 = RectChart[0] + xstep * (j+1);

                        y2 = RectChart[3] - fChartHeight * ((newitems[j+1].Value - data_min) / (data_max - data_min));

                        if (y2 < RectChart[1])

                            y2 = RectChart[1];

                        if (y2 > RectChart[3])

                            y2 = RectChart[3];

                        vline.StartPoint = new Point(x1, y1);

                        vline.EndPoint = new Point(x2, y2);

                        data_geom.Children.Add(vline);

                    }


                    Path value_path = new Path();

                    value_path.StrokeThickness = 1;

                    if (i == 0)  

                        value_path.Stroke = new SolidColorBrush(Colors.Red);

                    else if (i == 1)

                        value_path.Stroke = new SolidColorBrush(Colors.Green);

                    else if (i == 2)

                        value_path.Stroke = new SolidColorBrush(Colors.Blue);

                    else

                        value_path.Stroke = new SolidColorBrush(Colors.Yellow);

                    value_path.Data = data_geom;

                    canGraph.Children.Add(value_path);

                }

            }

        }

위와 같이 직접 Canvas에 직접 차트를 그릴경우에는 약 3000개의 데이터를 현시하는데 있어 2~3초정도 소요됨을 확인하였습니다. 

차트를 이용하여 대용량을 현시하고자 할 경우에는 Canvas에 직접 그리는 것을 권고합니다.

Canvas를 이용하여 WinRT용 C# 차트 응용프로그램 실행 화면.


Step 4. enuSpace 서버(웹 서버)에 데이터를 연동

enuSpace for mars 버젼은 다기능 통합 소프트웨어 플랫폼입니다. enuSpace(엔유스페이스) 웹서버와 데이터를 연동하도록 하겠습니다. 웹서버에 연동시 라즈베리 파이에서 1초에 약 2700샘플링 데이터를 취득합니다. 이때 모든 데이터를 서버에 연동하는 것은 서버에 큰 부담을 안겨줄수 있습니다. 이에 1초에 2회의 대표값 데이터를 전송하도록 구성 하였습니다.

라즈베리 파이에서 Windows 10 IoT Core App에 웹 통신을 이용한 디바이스 등록, 변수 등록 페이지를 추가하여 센서값을 연결하기 용이하도록 구성하여 보았습니다. 

알람 설정과 서버에 전송 설정을 수행하는 UI도 함께 구성하였습니다. 


Windows 10 IoT Core용 WinRT C# 을 이용한 웹 통신 처리 코드는 아래와 같이 사용할 수 있습니다. 

enuSpace 서버(웹 서버)에 데이터를 전송하는 샘플 코드입니다. 

WinRT C# http request POST 전송 처리.

        public async void SendSensorData(int iVal1, int iVal2)

        {

            if (m_bSendValue)

            {

                String url = "http://169.254.60.226:8080/setvalue_package";

                var text = "{\"" + "@" + m_DeviceID + ".A0" + "\":\"" + iVal1.ToString() + "\",\"" + "@" + m_DeviceID + ".A1" + "\":\"" + iVal2.ToString() + "\"}";

                var strParam = "tagid_list=" + text;

                String response = await getResponse(url, strParam);

                status.Text = response;

            }

        } 


        private async Task<String> getResponse(String url, string data)

        {

            try

            {

                WebRequest request = WebRequest.Create(url);


                request.ContentType = "application/json";

                byte[] byteArray = System.Text.Encoding.UTF8.GetBytes(data);

                request.ContentType = "application/x-www-form-urlencoded";

                request.Method = "POST";


                using (var requestStream = await request.GetRequestStreamAsync())

                {

                    System.IO.StreamWriter writer = new System.IO.StreamWriter(requestStream);

                    writer.Write(data);

                    writer.Flush();


                    using (WebResponse response = await request.GetResponseAsync())

                    {

                        using (System.IO.Stream responseStream = response.GetResponseStream())

                        {

                            System.IO.StreamReader reader = new System.IO.StreamReader(responseStream);

                            String answer = reader.ReadToEnd();

                            return answer;

                        }

                    }

                }

            }

            catch (Exception ex)

            {

                status.Text = ex.Message;

                return "";

            }

        }

웹 서버에 POST 명령으로 데이터를 전송하기 위해서 WebRequest를 이용한다. 그리고 비동기식 처리를 통하여 데이터 취득에 영향을 최소화 합니다.


Step 5. enuSpace 통합 플랫폼을 이용하여 데이터 현시를 위한 픽쳐 화면을 구성합니다. 

enuSpace 통합 플랫폼에서 제공하는 웹 서버를 구동하고, 라즈베리 파이의 응용프로그램을 통하여 디바이스 추가 및 변수 추가를 수행합니다.

라즈베리 파이의 응용 프로그램에서 추가한 디바이스와 변수 정보를 DB 관리자를 통하여 확인하실수 있습니다.

라즈베리 파이의 응용 프로그램으로부터 주기적으로 데이터 들어오는 확인합니다. 정상적으로 데이터가 전달되는 값을 확인하였다면, 픽쳐를 생성하고 아날로그 Value와 알람 Value값을 차트를 추가하여 현시합니다.

차트는 시리즈 2개를 추가하고, 각각의 DB Tag 정보를 연결을 수행하면 아래 그림과 같이 센서 아날로그 신호 값을 실시간 데이터로 확인 할 수 있습니다.

아래 그림은 라즈베리파이에서 전송한 센서 데이터가 enuSpace 서버에서 현시되는 결과의 모습입니다.


추가 팁 

Windows IoT Core Remote Server 사용 Tip.

라즈베리 파이 응용 프로그램을 리모트 컨트롤을 수행하기 위해서 아래 그림의 링크를 따라서, 어플을 설치후 라즈베리 파이의 IP주소를 입력하여 실행을 수행하면, 리모트 컨트롤이 가능합니다.


+ Recent posts