enuSpace for mars(2017)에서 확장용 web 모듈 사용하기


enuSpace for mars(2017)버젼부터 확장용 web 모듈을 추가하여 개발할 수 있는 기능을 제공합니다. 본 내용은 개발자에게 web 모듈 확장 프로젝트 사용 방법에 대하여 설명합니다.

확장 웹모듈 인터페이스 구조도

동작 방법 : enuSpace가 설치된 동일한 디렉토리에 제공되는 샘플용 프로젝트(WebExtension.sln)를 컴파일하여 WebExtension.dll 생성, enuSpace의 실행 프로그램 위치에 복사하여 붙여넣기를 수행합니다. 

enuSpace 프로그램을 실행하고 기존의 프로젝트를 로드하거나 신규 프로젝트를 생성후 웹 서버 시작 버튼을 클릭하면 enuSpace프로그램에서 확장 WebExtension.dll 파일을 로드합니다.  

사용 방법 : 기본 제공되는 RASTful API 이외의 함수에 대해서는 WebExtension.dll의 Requst함수에 의하여 처리되므로, 개발자가 클라이언트에서 전달된 데이터의 데이터를 전달받아 요청된 명령에 따른 처리를 수행하면 됩니다. 

아래 기본 샘플은 사용자가 웹서버를 통하여 전달받은 데이터를 확장 모듈에서 ECHO 데이터를 반환하는 샘플이다. 

void GetParamValue(CString strInput, CString *strAttr, CString *strParam)

{

int ipos = strInput.Find('=');

if (ipos > 0)

{

CString strElement;

*strAttr = strInput.Left(ipos);

*strParam = strInput.Right(strInput.GetLength() - ipos - 1);

}

}

char* WCToMB(CString str)

{

char * temp = new char[_tcslen(str) * 2 + 1];

WideCharToMultiByte(CP_ACP, 0, str, (int)_tcslen(str) + 1, temp, (int)_tcslen(str) * 2 + 1, 0, 0);

return temp;

}

extern "C" __declspec(dllexport) void Request(WebSocketObject* pSocketObject, wchar_t* funtionname, wchar_t* contents)

{

// SAMPLE CODE

CString strParam = contents;

CString Seperator = _T("&");

int charpos = 0;

CString token;

token = strParam.Tokenize(Seperator, charpos);


// 개별 입력 속성및 값을 취득하는 SAMPLE

while (token != L"")

{

token.Trim();

CString attribute; // attribute값 취득

CString value; // value값 취득

GetParamValue(token, &attribute, &value);

token = strParam.Tokenize(Seperator, charpos);

}


// 반환값 코드.

CString strResult;

strResult.Format(L"RESULT:%s, %s" ,funtionname, contents);

char* pstr = WCToMB(strResult.GetBuffer(0));


pSocketObject->response.sendlen = strlen(pstr);

pSocketObject->response.pSendBuffer = pstr;

pSocketObject->response.connent_type = L"application/json; charset=UTF-8";

return;

클라이언트 웹 브라우져에서 주소줄에 다음과 같이 입력하였을 경우, http://127.0.0.1:8080/myfunction?dfdf=444&dfefe=999

아래 그림과 같은 결과를 획득할 수 있다. 

주) 반환값을 전달할 때 char*의 메모리 값을 동적으로 생성하였으며, 별도의 메모리 해제를 수행하지 말아야 한다. enuSpace 웹 서버모듈에서 처리가 완료되면, 메모리를 자동으로 해제를 수행하기 때문이다. 


SVG와 JavaScript를 이용한 다이나믹 심볼 제작 (온도 Gauge)


라이브러리 생성

엔유스페이스를 실행하고 Project Explorer에서 Library하위에서 Logic선택후 Add New logic Library를 선택합니다. 라이브러리 파일이름을 입력후 Create를 수행하면, 새로운 로직파일이 생성됩니다.

생성된 파일을 선택후 팝업메뉴를 이용하여 Add Node를 통하여 새로운 심볼을 생성합니다. 생성된 심볼을 디자인합니다. 디자인 객체를 이용하여 아래 그림과 같이 디자인을 수행합니다. 

각각의 객체의 속성에 id를 위 그림과 같이 설정합니다. 핀객체에 대해서는 name의 속성값을 설정합니다. 핀객체는 로직블럭과 로직블럭간의 연결선을 이용하여 연결이 가능합니다. 연결선을 이용하여 핀객체 연결시 데이터 전송 역할을 수행합니다.


심볼의 자체에 이벤트 함수를 등록합니다. _ontask()함수에 아래의 코드를 추가합니다. 입력핀의 객체 변수값을 출력핀의 객체 변수값으로 할당하는 코드입니다.

function _ontask()

--TODO Add your lua script code here

output = input

end

심볼에 입력데이터의 min, max를 정의하는 변수를 추가합니다. 

float min = 0

float max = 100

다음으로 ID_GAGUE객체를 선택하고 이벤트함수 _ontaskview()함수를 추가합니다. 이때 추가할 스크립트는 JavaScript를 이용하여 추가합니다. JavaScript를 이용하는 경우에는 웹 현시에 있어 동일한 스크립트가 동작되어 수행됩니다.

function _ontaskview()

{

//TODO Add your javascript code here

var datagap = max-min;


var data = output;

if (data < min)

data = min;

else if (data >max)

data = max;


var h = (43 * (data-min)) / (max - min);

ID_GAUGE.height = h;


ID_LABEL_VALUE.textContent = output.toString();

ID_LABEL_MIN.textContent = min.toString();

ID_LABEL_MAX.textContent = max.toString();

}

위 코드를 간단하게 보면, 출력 데이터의 값이 심볼변수를 설정한 min, max의 범위안에서 처리가 되도록 조건을 걸어줍니다. 다음으로 ID_GAUGE의 초기 Height값 43을 기준으로 출력 데이터의 실제 게이지의 높이값을 계산하여, ID_GAUGE객체의 높이값으로 치환합니다.

ID_LABEL_VALUE, ID_LABEL_MIN, ID_LABEL_MAX의 값 현시를 위한 코드를 추가합니다. 이와같은 방법으로 SVG 그래픽 과 JavaScript 코드를 이용하여 다양한 그래픽 심볼 라이브러리를 생성할 수 있습니다. 


화면 드로잉

생성된 라이브러리를 이용하여 픽쳐페이지에 적용합니다. Project Explorer에서 Picture를 선택후 Add New Picture Item 메뉴를 선택하여 픽쳐페이지를 생성합니다.

생성된 픽쳐페이지에 생성한 라이브러리를 마우스를 이용하여 드래그 & 드랍으로 객체를 생성합니다. 선택객체를 더블클릭하여 Value Table의 입력값을 조정하여 정상적으로 라이브러리가 생성되었는지 확인합니다.

입력값을 50으로 설정시 게이지바의 위치가 50의 위치하였음을 확인할 수 있습니다.

리본 메뉴의 Data/Communication에서 웹서버 기동 메뉴를 클릭하여, 웹서버를 기동한 후 웹브라우져를 통하여 제작한 픽쳐페이지가 정상적으로 현시되는지 확인합니다. 


다양한 형태의 게이지를 생성하여, 아래 그림과 같이 적용 및 활용할 수 있습니다.

enuSpace for Mars(2017)은 HMI/SCADA/DCS/IoT분야에 강력한 기능을 활용하여 적용할 수 있습니다. 


본 기술개발의 일부는 중소기업청의 창업기술개발사업의 일환으로 수행된 결과입니다.


enuSpace for Mars(2017) - 주요기능 소개 (ENU Co.ltd)


추가되는 기능


1. IoT 디바이스를 로직 심볼 형태로 제공합니다.

2. 웹 서버기능이 엔유스페이스 프로그램에서 자체 지원합니다.

3. IoT 디바이스 심볼과 로직 블럭을 이용하여 연산 및 제어처리가 가능합니다. 

4. 웹 브라우져를 통하여 로직 처리 결과 확인이 가능합니다. 


상세기능.

IoT 디바이스를 심볼로 구성하여 그래픽적인 연산처리를 수행한후 실시간 디바이스의 센서값을 웹을 통하여 현시 및 제어를 수행할 수 있습니다. 

enuSpace for Mars의 데이터 인터페이스 구성도

enuSpace for Mars는 

Raspberry PI 전용 Windows 10 IoT Core기반의 전용 응용프로그램,

Arduino의 StandardFirmata 프로그램을 설치하여 연계를 수행합니다.

디바이스 등록과정을 거친후 서버에서 모든 제어 및 모니터링을 수행합니다.

● Raspberry PI 응용프로그램을 이용한 디바이스 등록 방법

사용자 로그인 및 웹서버 주소 설정 과정

디바이스 등록 과정

디바이스 등록후 StandardFirmata를 이용한 Arduino 시리얼 통신 연결 

디바이스 심볼 객체를 이용하여 디바이스 모니터링 및 제어기 만들기.

※ 디바이스 심볼은 기 제공되는 심볼을 이용하거나, 사용자가 직접 심볼을 제작하여 등록후 사용이 가능합니다. 


● 디바이스 심볼 만들기 방법

ProjectExplorer에서 Library하위 Logic에서 신규 라이브러리 파일을 생성하고, Add Node 메뉴를 이용하여 신규 라이브러리를 생성합니다.

생성된 신규 라이브러리에 관련 이미지 또는 기본 드로잉 객체를 이용하여 심볼을 디자인 합니다. 디자인된 심볼에 외부의 데이터 인터페이스를 위한 PIN객체를 추가하고, PIN객체의 속성 name을 디바이스의 A0, A1, A2, A3, A4, A5, D0, ~, D13까지 생성합니다. 

본 단계는 사용자가 직접 제어를 위한 방법이며, 본 프로그램에서 제공되는 라이브러리를 이용하는 경우에는 별도의 작업이 필요치 않습니다.  

IoT 디바이스 라이브러리 생성 방법

라벨의 객체에 실시간 디바이스 센서값 표현하기 위해서는 신규 text 객체를 추가합니다. 추가된 객체에 javascript 함수를 추가합니다. javascript는 웹 모니터링시에 동일한 형태로 브라우져에서 현시됩니다.

텍스트 객체에 스크립트를 추가하는 방법

위 그림과 같이 ontaskview() 함수를 추가하고, textContent = A0.toString(); 코드를 추가함으로서 A0의 변수값을 text의 라벨값으로 현시하기 위한 코드를 추가합니다.

위와 같이 사용자가 다양한 형태의 게이지, 컴포넌트 등을 구성하여 라이브러리를 구성할 수 있습니다.


● 디바이스 심볼활용 방법 (픽쳐 드로잉)

ProjectExplorer에서 Picture의 하위에 Add New Picture Item 버튼을 클릭하여 새로운 픽쳐페이지를 생성합니다. 생성된 페이지에 Logic 객체와 HMI 객체를 이용하여 화면 드로잉을 수행합니다. 

IoT 디바이스 심볼 객체를 이용한 드로잉 방법

등록된 디바이스에 대하여 IoT 디바이스 심볼 객체에 대한 속성 설정을 통한 화면 연계를 수행합니다.

디바이스 심볼을 선택하고, interface 속성값을 true 설정, interface-id의 속성값을 디바이스 키값을 입력합니다. 인터페이스 설정이 완료되면 디바이스의 정보가 심볼객체를 통하여 데이터가 현시됩니다.


모든 화면 구성 및 그래픽적인 로직화면 구성이 완료되었다면 저장을 수행합니다.


● enuSpace for Mars의 웹 서버연동

위 작업이 마무리 되었으면, 엔유스페이스 프로그램이 실행된 컴퓨터의 서버주소를 브라우져를 실행하여 주소를 입력합니다.

서버의 정보가 클라이언트에 실시간 데이터가 모니터링됨을 확인 할 수 있습니다.

enuSpace for Mars(2017) 실행 화면

크롬 브라우져에서 enuSpace에 접속한 화면


본 기술개발의 일부는 중소기업청의 창업기술개발사업의 일환으로 수행된 결과입니다.

Windows 10 IoT Core Arduino Firmata 연계


1. Create a New Project (Visual Stuidio 2015)

   WinRT Universal Windows Template 선택하여 신규 프로젝트를 생성.


2. Windows-Remote-Arduino Nuget Package 설치.

   Project explorer 오른쪽 버튼 클릭 팝업메뉴에서 Manage Nuget Package 선택.

   Windows Remote Arduino 검색 및 설치.


3. Arduino Firmata 프로그래밍

   관련 소스 : https://github.com/firmata/arduino/blob/master/examples/StandardFirmata/StandardFirmata.ino


4. Windows 10 IoT Core 응용프로그램 개발 (WinRT C#)

   WinRT C# Firmata 프로그래밍.

   관련 문서 : https://github.com/ms-iot/remote-wiring



Windows 10 IoT Core C# Using SQLite


Step 1. Install the SQLite Visual Stuidio extension for Visual Studio 2015

http://sqlite.org/download.html


Step 2. SQLite.Net-PCL NuGet Installation.


Step 3. Add Reference


Step 4. using SQLite.Net.Attributes;


Step 5. SQLite DB Connection and Query.


SQLite DB Create DB Table Sample.

        public class Configuration

        {

            [PrimaryKey, AutoIncrement]

            public int Id { get; set; }

            public string Attribute { get; set; }

            public string Value { get; set; }

        }


        void LoadSqliteDB()

        {

            String path = Path.Combine(Windows.Storage.ApplicationData.Current.LocalFolder.Path, "enuspace.sqlite");

            m_conn = new SQLite.Net.SQLiteConnection(new SQLite.Net.Platform.WinRT.SQLitePlatformWinRT(), path);

            m_conn.CreateTable<Configuration>();

        }

위 코드를 이용하면, 테이블명은 Configuration, 컬럼명은 Id, Attribute, Value에 해당하는 테이블을 생성한다.


SQLite DB Check Table Exists and Create DB Table

        void LoadSqliteDB()

        {

            String path = Path.Combine(Windows.Storage.ApplicationData.Current.LocalFolder.Path, "enuspace.sqlite");

            m_conn = new SQLite.Net.SQLiteConnection(new SQLite.Net.Platform.WinRT.SQLitePlatformWinRT(), path);


            if (TableExist("Configuration", m_conn) == false)

                m_conn.CreateTable<Configuration>();

        }


        public bool TableExist(String tableName, SQLite.Net.SQLiteConnection conn)

        {

            String tableExistsQuery = "SELECT name FROM sqlite_master WHERE type='table' AND name='" + tableName + "';";

            String result = conn.ExecuteScalar<string>(tableExistsQuery);


            if (result == tableName)

                return true;

            else

                return false;

        }

프로그램 기동시 테이블의 존재유무를 체크하고자 하는 경우에는 ExecuteScalar를 이용하는 방법이 있다. 이때 리턴받는 문자열 result값에는 테이블이 존재하는 경우, 테이블 이름이 전달되며, 테이블 이름이 없다면 null이 리턴된다.


SQLite DB Table data query

        void LoadSqliteDB()

        {

            String path = Path.Combine(Windows.Storage.ApplicationData.Current.LocalFolder.Path, "enuspace.sqlite");

            m_conn = new SQLite.Net.SQLiteConnection(new SQLite.Net.Platform.WinRT.SQLitePlatformWinRT(), path);


            if (TableExist("Configuration", m_conn) == false)

                m_conn.CreateTable<Configuration>();

            else

            {

                var query = m_conn.Table<Configuration>();

                 foreach (var message in query)

                {

                    String strAttribute = message.Attribute;

                    String strValue = message.Value;

                }

            }

        } 

데이터 테이블 쿼리 요청에 따른 결과값 확인을 수행한다.


SQLite DB Data Insert


        m_conn.Insert(new Configuration() { Attribute = "auto-login", Value = "true" });



SQLite DB Data Update


                        var query = m_conn.Table<Configuration>().Where(x => x.Attribute == "auto-login");

                        var result = query.ToList();

                        if (result.Count == 0)

                            m_conn.Insert(new Configuration() { Attribute = "auto-login", Value = "true" });

                        else

                        {

                            result[0].Value = "true";

                            m_conn.Update(result[0]);

                        }

 


SQLite DB Data Delete


       var query = m_conn.Table<Configuration>().Where(x => x.Attribute == "user-pw");

        var result = query.ToList();

        foreach (var item in result)

        {

              m_conn.Delete(item);

        }

 



SQLite DB 조건을 이용하여 Query 정보를 확인.

 

         var query = m_conn.Table<Configuration>().Where(x => x.Attribute == "auto-login");

         var result = query.ToList();

         foreach (var item in result)

         {

                String test1 = item.Attribute;

                String test2 = item.Value;

        }






Windows 10 IoT Core Remote Server 사용 Tip.

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

Windows 10 IoT Core에서 WinRT C#용 Chart


1. WinRTXamlToolkit.Controls.DataVisualization.UWP를 이용한 Chart.


차트를 드로잉하기 위해서 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;

            }

        } 

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

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

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


2. 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# 차트 응용프로그램 실행 화면.



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주소를 입력하여 실행을 수행하면, 리모트 컨트롤이 가능합니다.


enuSpace for Mars의 기능중 하나인 Web Data 인터페이스 작업 및 Big data 처리


아래 동영상을 확인하시면, 대용량 객체현시에 대한 각 브라우져의 특성을 보실수 있습니다. (16,384개 데이터)


Web Data Visualization 동영상 보기.

Step 1. Wave Model Task 생성 및 등록.

enuSpace에서 제공되는 Task 생성 SDK를 이용하여 Wave 데이터 파일을 생성하는 dll 파일을 생성합니다. 


double pinpower[8][8][26][16][16];

float initValue = 1.0f;

void MAIN()

{

initValue = initValue + 0.1;

//////////////////////////////////////////////////////////////

for (int x = 0; x < 8; x++)

{

for (int y = 0; y < 8; y++)

{

for (int k = 0; k < 26; k++)

{

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

{

for (int j = 0; j < 16; j++)

{

pinpower[x][y][k][i][j] = 1.0 + sin(double((x * 16) + i + (y * 16) + j) / 3.141592 / 5 + initValue);

}

}

}

}

}

SetArrayValue(L"@CORE.pinpower[0][0][0][0][0]", pinpower, DEF_DOUBLE, 8 * 8 * 26 * 16 * 16);

}


배열변수를 이용하여 sin() 함수의 값을 매 주기마다 업데이트를 수행합니다.

저장된 배열 정보는 enuSpace의 데이터 베이스 메모리에 SetArrayValue() 함수를 이용하여 고속 복사를 수행합니다.


Step 2. 데이터베이스 매니져를 이용하여 배열 변수를 생성하여 Task의 연산 결과와 연동할 수 있도록 등록을 수행합니다.


Step 3. enuSpace의 Project Explorer의 팝업메뉴를 이용하여 Add 3d picture item을 이용하여 3D 픽쳐를 생성합니다.

생성한 픽쳐에 배열 변수값을 확인할 수 있는 Terrain 객체를 추가합니다.


생성한 객체의 Taskview() 함수에 그래픽 객쳍와 연동을 위한 루아 스크립트를 추가합니다. 루아 스크립트는 enuSpace에서 고속 데이터 처리를 수행합니다.

function _ontaskview()

memcpy(&data[0][0],&@CORE.pindis[0][0],128*128)

end


데이터 베이스의 메모리 주소와 복사하고자 하는 데이터의 주소값을 위 코드와 같이 입력합니다. Run 버튼을 이용하여 Wave 데이터 값을 업데이트 수행합니다.


Step 4. enuSpace에 Add new picture item을 선택하여 2D 화면 구성을 위한 페이지를 새성합니다.

사각형 객체를 16 by 16으로 나열하여 새성을 수행합니다. 생성된 객체를 선택후 Taskview() 함수에 Javascript 함수를 추가합니다.

직접 객체를 생성하기 어려운 경우에는 스크립트를 이용하여 동적으로 객체를 생성하고 스크립트 내용을 동적으로 적용하면 간단하게 생성할 수 있습니다. 

function _ontaskview()

{

var value_obj = document.getElementById("ID00_13_9_7");

var tag_val = GetTagValue("@CORE.pinpower[0][0][13][9][7]");

value_obj.setAttribute("fill", GetValueColor(tag_val));

}

// 전역 javascript 함수 => 리본 메뉴의 Edit Java Script 를 이용하여 추가 수행.

function GetValueColor(fValue)

{

fValue = fValue * 50;

var color;


if (fValue<0) 

{ color = "rgb(0,0,0)"; }

else if (fValue>0 && fValue <=5) 

{ color = "rgb(15,75,165)"; }

else if( fValue>5 && fValue <=10 )

{color = "rgb(30,110,200)"; }

else if( fValue>10 && fValue <=15 )

{color = "rgb(60,160,240)"; }

else if( fValue>15 && fValue <=20 )

{color = "rgb(80,180,250)"; }

else if( fValue>20 && fValue <=25 )

{color = "rgb(130,210,255)"; }

else if( fValue>25 && fValue <=30 )

{color = "rgb(160,240,255)"; }

else if( fValue>30 && fValue <=35 )

{color = "rgb(200,250,255)"; }

else if( fValue>35 && fValue <=40 )

{color = "rgb(230,255,255)"; }

else if( fValue>40 && fValue <=45 )

{color = "rgb(255,250,220)"; }

else if( fValue>45 && fValue <=50 )

{color = "rgb(255,232,120)"; }

else if( fValue>50 && fValue <=55 )

{color = "rgb(255,192,60)"; }

else if( fValue>55 && fValue <=60 )

{color = "rgb(255,160,0)"; }

else if( fValue>60 && fValue <=65 )

{color = "rgb(255,96,0)"; }

else if( fValue>65 && fValue <=70 )

{color = "rgb(255,50,0)"; }

else if( fValue>70 && fValue <=75 )

{color = "rgb(225,20,0)"; }

else if( fValue>75 && fValue <=80 )

{color = "rgb(192,0,0)"; }

else

{color = "rgb(150,0,0)"; }

return color

}


사각형 객체의 ID값을 이용하여 객체를 획득후, 색상값을 표현하기 위한 데이터베이스 메모리 데이터의 값을 가져옵니다. 가져온 데이터를 이용하여 값을 Color 로 변환하는 함수를 통하여 내부 색상값을 변경합니다.

스크립트를 이용하여 동적 객체 생성 및 스크립트 등록.

Step 5. Web publish 메뉴를 이용하여 Web publish 수행.

Web browser를 이용하여 enuSpace 서버에 접속을 수행합니다. 웹 브라우져 Chrome, Firefox, MS edge를 이용하여 접속하여 서버 데이터의 그래픽 가시화를 수행합니다.

웹 브라우져를 이용한 대용량 데이터 처리를 위해서는 enuSpace에서 제공하는 API  gatvalue_package 함수를 활용하였으며 Web Worker를 이용합니다. 16 * 16 * 8 = 2048개의 묶음데이터에서 대하여 8회(2048*8 = 16384개) 분활하여 서버에 데이터를 요청하여 전송받아서 처리를 수행합니다.


enuSpace for Mars Terrain 객체의 데이터 가시화 작업


Terrain 객체의 속성 설정에 따른 가시화 결과.


DB 등록 및 Task 모델 생성.

* enuSpace for Mars에서 제공되는 모델 Scheduling 기능을 이용하여 변수값을 생성합니다. C++, Fortran 을 이용하여 모델을 생성할 수 있습니다. 

3D 그래픽 객체 Terrain 객체를 생성합니다.

* Terrain 객체의 subdivision_x, y의 값을 각각 15, 24로 설정합니다. 

* 생성된 Terrain 객체의 주기적인 데이터 업데이트를 수행하는  _ontaskview() 함수에 인터페이스 내용을 추가합니다.

* 객체의 데이터 인터페이스를 위해서는 data_0_0에서 부터 data_14_23 변수명을 통하여 인터페이스를 수행합니다.


function _ontaskview()


    data_0_0=@CORE.flux_1_1_1

    data_1_0=@CORE.flux_2_1_1

    data_2_0=@CORE.flux_3_1_1

    data_3_0=@CORE.flux_4_1_1 

   ~~~~

   data_12_23=@CORE.flux_13_1_24

   data_13_23=@CORE.flux_14_1_24

   data_14_23=@CORE.flux_15_1_24


end


data_0_0=@CORE.flux_1_1_1 // DB의 변수값을 Terrain 객체의 변수값에 할당을 수행합니다.


RUN 버튼을 클릭하여, 결과를 확인합니다. 


Terrain 객체의 속성

transparency : 1.0

terrainType = contour

elevationType = color+height

minElevation : 0

maxElevation : 1

colorElevation : 

rgb(10,50,120); 

rgb(15,75,165); 

rgb(30,110,200); 

rgb(60,160,240); 

rgb(80,180,250); 

rgb(130,210,255); 

rgb(160,240,255); 

rgb(200,250,255);

rgb(230,255,255);

rgb(255,250,220);

rgb(255,232,120);

rgb(255,192,60);

rgb(255,160,0);

rgb(255,96,0);

rgb(255,50,0);

rgb(225,20,0);

rgb(192,0,0);

rgb(165,0,0)




Terrain 객체의 속성

transparency : 1.0

terrainType = contour bar

elevationType = color+height

minElevation : 0

maxElevation : 1



Terrain 객체의 속성

transparency : 0.5

terrainType = contour bar

elevationType = color+height

minElevation : 0

maxElevation : 1



Terrain 객체의 속성

transparency : 1

terrainType = bar

elevationType = color+height

minElevation : 0

maxElevation : 1



Terrain 객체의 속성

transparency : 1

terrainType = point

elevationType = color+height

minElevation : 0

maxElevation : 1



solid : false



+ Recent posts