enuSpace 시뮬레이션 기능 및 Data Visualization 사용방법


본 강좌에서는 enuSpace for mars를 이용하여 시뮬레이션을 수행하는 방법에 대하여 설명합니다. 

enuSpace 학생용 버젼 다운로드 : http://enuspace.tistory.com/entry/enuSpace-for-Mars-2017


enuSpace for mars에서 시뮬레이션 또는 task를 개발함에 있어 2가지 형태의 모델로 구성될 수 있습니다. enuSpace for mars의 ontask함수를 활용한 방법과 외부의 task의 모듈을 연동하는 방법입니다.


Lesson5 sample : lesson5.zip


1. 로직 라이브러리를 이용한 flowbased programming 방법.

2. 외부 모델을 이용한 연동 방법.


동영상 미리보기


New Project


새로운 프로젝트를 생성하기 위해서 Home->New->New Project메뉴를 이용하여 새로운 프로젝트를 생성합니다.



Simulation Time Setup


새로운 프로젝트 생성을 완료하였다면, 리본메뉴 Data/Communication->System Configuration메뉴를 선택하여 System Configuration 대화창의 속성을 설정합니다. 시뮬레이션용으로 설정하기 위해서는 Time의 속성중 Simulation Time을 설정합니다. Simulation Time을 설정하게 되면 enuSpace에서 기동되는 시작시간의 정보는 0초부터 동작을 수행합니다. 

System Time옵션에서 Simulation Time옵션으로 변경 수행시 프로젝트에 기존에 누적된 히스토리 데이터베이스가 모두 제거되어 초기화 됩니다. 프로젝트 로드시에도 기존의 히스토리 데이터베이스를 초기화 합니다.

참고로 System Time의 설정은 현재시간(UTC)을 기준으로 모든 데이터가 생성되고  히스토레 데이터에 누적됩니다.  



Simulation Scheduling


enuSpace for mars의 task()함수를 사용하는 경우에는 flowbased programming을 통하여 동작을 수행합니다. 내부 task()함수는 스케쥴링 속성값에 따라서 주기적인 연산을 호출하도록 구성되어 있습니다. 속성 설정하기 위해서 Project Explorer창 Task하위 logic_svg 아이템을 선택합니다. logic_svg 속성창 중 Cycle (Hz) 속성은 1초에 1, 2, 3, 4, 6, 8, 12, 24회 호출 주기에 대한 설정입니다. Task In/Out의 속성은 선택한 Task에 대하여 연산을 수행할 것인지, 제외할것인지를 설정합니다. 



시뮬레이션 모델을 제작하다보면 task()함수에서 delta T값을 이용하는 경우가 있습니다. 전역변수 dt값을 이용하여 코드를 구현합니다.


function _ontask()

--TODO Add your lua script code here

local deltaT = dt

--bla bla ~~

end


외부 모델 연계 API

시뮬레이션 모델 개발시 기존의 코드를 재활용하거나 외부의 모델과의 인터페이스가 필요한 경우가 있습니다. enuSpace는 외부 모델과 연동하기 위한 인터페이스 모듈을 제공합니다. 외부 모델과 연계시 dynamic link library(dll)을 이용합니다.

외부 연동 인터페이스 모듈의 dll의 주요 함수는 아래와 같습니다.


extern "C" __declspec(dllexport) bool OnInit();

extern "C" __declspec(dllexport) bool OnLoad();

extern "C" __declspec(dllexport) bool OnUnload();

extern "C" __declspec(dllexport) bool OnTask();


각 함수는 아래와 같은 조건에서 호출됩니다.

OnInit() : 첫 기동시 호출되는 초기화 함수

OnLoad() : dll 로드수행시 호출되는 함수

OnUnload() : dll 언로드 수행시 호출되는 함수

OnTask() : 스케쥴링 주기에 따라서 호출되는 함수


OnTask()함수 구현부에는 시뮬레이션을 수행하고자 하는 알고리즘을 추가합니다. 작성된 코드와 enuSpace와 연동하기 위해서 인터페이스용 콜백함수 4개를 제공합니다. 


extern "C" __declspec(dllexport) void SetCallBack_SetValue(void fcbSetValue(wchar_t*, double));

extern "C" __declspec(dllexport) void SetCallBack_GetValue(DBDataStruct* fcbGetValue(wchar_t*));

extern "C" __declspec(dllexport) void SetCallBack_SetArrayValue(void fcbSetArrayValue(wchar_t*, void*, int, int));

extern "C" __declspec(dllexport) void SetCallBack_GetArrayValue(DBDataStruct* fcbGetArrayValue(wchar_t*));


특정 데이터베이스의 Tag값을 읽고 쓰기 위한 함수 포인터입니다. 본 함수는 dll로드 수행시 함수의 포인터로 전달됩니다. 전달된 함수 포인터 이용하여 값을 읽거나 쓰기를 수행합니다. 


구현부를 보시면 아래와 같습니다.


void (*g_fcbSetValue)(wchar_t*, double) = NULL;

DBDataStruct* (*g_fcbGetValue)(wchar_t*) = NULL;

void(*g_fcbSetArrayValue)(wchar_t*, void*, int, int) = NULL;

DBDataStruct* (*g_fcbGetArrayValue)(wchar_t*) = NULL;


extern "C" __declspec(dllexport) void SetCallBack_SetValue(void fcbSetValue(wchar_t*, double)) g_fcbSetValue = fcbSetValue; }

extern "C" __declspec(dllexport) void SetCallBack_GetValue(DBDataStruct* fcbGetValue(wchar_t*)) g_fcbGetValue = fcbGetValue; }

extern "C" __declspec(dllexport) void SetCallBack_SetArrayValue(void fcbSetArrayValue(wchar_t*, void*, int, int)) g_fcbSetArrayValue = fcbSetArrayValue; }

extern "C" __declspec(dllexport) void SetCallBack_GetArrayValue(DBDataStruct* fcbGetArrayValue(wchar_t*)) g_fcbGetArrayValue = fcbGetArrayValue; }


위 코드는 기 제공되는 코드의 예시입니다. 


CMap<CString, LPCWSTR, DBDataStruct*, DBDataStruct*> g_DBMapList;

void SetArrayValue(CString strVariable, void* pSrc, int iType, int iSize)

{

if (g_fcbSetArrayValue)

g_fcbSetArrayValue(strVariable.GetBuffer(0), pSrc, iType, iSize);

}

DBDataStruct* GetArrayValue(CString strVariable)

{

if (g_fcbGetArrayValue)

return g_fcbGetArrayValue(strVariable.GetBuffer(0));

return NULL;

}

void SetValue(CString strVariable, double fValue)

{

double fReturn = 0;

DBDataStruct* pData = NULL;

g_DBMapList.Lookup(strVariable, pData);

if (g_fcbSetValue && pData == NULL)

{

pData = g_fcbGetValue(strVariable.GetBuffer(0));

g_DBMapList.SetAt(strVariable, pData);

}

if (pData && pData->pValue)

{

switch (pData->itype)

{

case DEF_INT:

*(int*)pData->pValue = (int)fValue;

break;

case DEF_FLOAT:

*(float*)pData->pValue = (float)fValue;

break;

case DEF_DOUBLE:

*(double*)pData->pValue = fValue;

break;

case DEF_BOOL:

if (fValue == 1)

*(bool*)pData->pValue = true;

else

*(bool*)pData->pValue = false;

break;

case DEF_WCHAR:

break;

default:

break;

}

}

}

double GetValue(CString strVariable)

{

double fReturn = 0;

DBDataStruct* pData = NULL;

g_DBMapList.Lookup(strVariable, pData);

if (g_fcbGetValue && pData == NULL)

{

pData = g_fcbGetValue(strVariable.GetBuffer(0));

if (pData)

g_DBMapList.SetAt(strVariable, pData);

}

if (pData && pData->pValue)

{

switch (pData->itype)

{

case DEF_INT:

fReturn = *(int*)pData->pValue;

break;

case DEF_FLOAT:

fReturn = *(float*)pData->pValue;

break;

case DEF_DOUBLE:

fReturn = *(double*)pData->pValue;

break;

case DEF_BOOL:

if (*(bool*)pData->pValue == TRUE)

fReturn = 1;

else

fReturn = 0;

break;

default:

break;

}

}

return fReturn;

}


실제 인터페이스는 값을 읽기 쓰기위한 코드와 모델 연산로직 코드를 추가함으로서 외부 모델을 연계할 수 있습니다. 


모델 예)

모델의 실행 코드와 인터페이스 코드 예시


double display[16][16];

float initValue = 1.0f;


extern "C" __declspec(dllexport) bool OnTask()

{

initValue = initValue + 0.1;

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

{

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

{

display[x][y] = 1.0 + sin(double((x * 16) + (y * 16)) / 3.141592 / 5 + initValue);

}

}

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

return true;

}


sin파를 생성하여 데이터 인터페이스를 수행하는 코드를 작성하였습니다. 위와 같이 코드를 추가하고 컴파일을 수행하여 CoreTask.dll을 생성합니다. 모델 생성이 완료하였다면 데이터베이스와 연동하기 위한 tag id를 enuSpace 프로젝트에 추가를 수행합니다. SetArrayValue()함수는 배열 정보를 enuSpace에서 추가한 tag의(@CORE.display[0][0]) 값에 연산 결과를 전달하는 과정입니다. 


SetArrayValue() 함수의 파라미터 정보를 보면 변수명, 원본 데이터의 메모리 주소, 복사하고자하는 데이터 타입, 사이즈값입니다. 만약 단일 변수에 추가하고자 하는 경우에는 SetValue()함수를 이용합니다. 경우에 따라서 위의 예시에서 제공하는 SetValue()함수 등은 확장 및 변경하여 구현할 수 있습니다.


enuSpace for mars 데이터베이스 TAG 추가


enuSpace의 리본메뉴 Data/Communication->DB Show 버튼을 클릭합니다. 다음과 같이 DB Table의 정보가 나타나고 DB+ 버튼을 클릭하여 모델간의 인터페이스를 위한 TAG 변수를 추가합니다. 추가되는 최종 TAG ID는 @문자가 앞에 추가됩니다. @문자는 데이터베이스 TAG 변수와 전역변수와의 구분자로도 활용됩니다. 데이터베이스 TAG ID를 이용하여 스크립트에서 활용합니다.



정상적으로 데이터베이스 TAG가 추가완료 후 앞에서 제작한 외부 TASK(CoreTask.dll)를 enuSpace 프로젝트에 추가합니다. Project Explorer의 Task항목을 선택하고 팝업메뉴 Add Task를 선택하여 Task Module의 콤보박스에 CoreTask.dll을 선택합니다. 프로젝트 디렉토리 lesson5//Task//CoreTask에 앞에서 생성한 CoreTask.dll 파일이 존재하는 경우 자동으로 콤보박스에서 선택이 가능하도록 제공이 됩니다. 



디렉토리의 구조 및 외부 TASK의 위치는 아래 그림과 같습니다.



TASK가 정상 등록이 되었다면, TASK를 기동하기 위해서 리본메뉴 Home->Script Operation의 Run 버튼을 클릭합니다. 연산이 정상적으로 처리되고 있는지를 확인하기 위해서 리본메뉴의 리본메뉴의 Data/Communication->DB Show메뉴를 선택하여 데이터베이스의 실시간 값을 확인합니다. 



아래 그림과 같이 차트 버튼을 클릭하여 팝업 Trend를 이용하여 데이터값을 확인할 수 있습니다. 차트사용 방법은 별도의 강좌에서 설명하도록 하겠습니다. 



3D data visualization


앞에서 제작한 배열변수의 값을 3차원 디스플레이에 디스플레이 하기 위해서 픽쳐파일 core_display.x3d 파일을 추가합니다. 추가된 x3d 파일을 선택후 Terrain객체를 추가합니다. Terrain 객체의 속성중 subdivision_x, subdivision_y의 값을 데이터베이스 TAG의 배열정보 16, 16으로 설정합니다. subdivision의 값을 설정함에 따라 내부의 double형 데이터 변수 data[16][16]가 생성됩니다.



추가된 Terrain 객체에 javascript 또는 lua script를 이용하여 데이터베이스 tag의 값을 그래픽 객체의 디스플레이 객체에 전달하여 실시간 데이터를 디스플레이 하기 위해서 아래 예시 코드와 같이 memcpy() 함수를 이용합니다. memcpy() 함수를 이용하지 않고 개별적인 인터페이스는 

data[0][0] = @CORE.display[0][0];

data[0][1] = @CORE.display[0][1]; ... 와 같이 코드를 작성할 수 있습니다.


* memcpy함수 사용시 변수의 타입과 변수 사이즈 입력에 주의하여야 한다. 잘못된 인자의 사용으로 인한 메모리 릭 또는 비정상 종료가 발생할 수 있습니다.    


ex) lua script

function _ontaskview()

--TODO Add your lua script code here

memcpy(&data[0][0],&@CORE.display[0][0],16*16)

end

ex) javascript

function _ontaskview()

{

//TODO Add your javascript code here

memcpy(&data[0][0],&@CORE.display[0][0],16*16);

}



2D data visualization


앞 강좌에서 기초객체의 속성값을 이용하여 2D 그래픽에 가시화하는 방법에 소개하였습니다. 간략히 살펴보면 추가된 객체를 선택하고 ontaskview의 함수를 추가합니다. enuSpace는 javascript와 lua script를 지원하며 각각의 예시는 아래와 같습니다.


function _ontaskview()

{

//TODO Add your javascript code here

textContent = @CORE.display[0][0];

}

function _ontaskview()

--TODO Add your lua script code here

text = string.format("%f", @CORE.display[0][0])

end


데이터베이스의 tag 값을 가져오기 위해서는 데이터베이스 tag id를 이용합니다. 데이터베이스 변수는 첫문자에 @문자를 포함합니다. 



데이터베이스의 값을 가져와 Text 객체의 값으로 할당하는 예시 화면.

사각형의 객체를 가져와 연산 결과를 높이값으로 표현하기 위해서 아래와 같이 코드를 추가합니다.


function _ontaskview()

{

//TODO Add your javascript code here

height = @CORE.display[0][15]*50;

}



시뮬레이션 상태값 저장 및 복원 (Snap, Reset) 기능


enuSpace 시뮬레이션 기능은 현재 설정된 상태값을 저장하고 저장된 상태값에 대한 복원 기능을 제공합니다. 상단의 리본메뉴 Home->Condition의 Snap 버튼과 Reset 버튼을 이용합니다. 


다음 강좌에서는 Trend 객체와 히스토리 팝업 Trend의 기능 및 사용방법에 알아보겠습니다.

-이엔유 주식회사- 

enuSpace for Mars를 이용한 2D, 3D Data Visualization 작업


실행 결과 동영상


샘플 Task 생성 및 등록.

* enuSpace for Mars는 뛰어난 Timing Scheduling 기능을 제공합니다. C++, Fortran 을 이용하여 생성한 dll을 정확한 타이밍 스케쥴러를 통하여 Task 연산이 가능하도록 적용하였습니다.

* 연산된 결과는 메모리 기반의 데이터베이스와 연계되어 빠른 인터페이스가 가능하도록 적용하였습니다. 

* 생성된 모델을 스케쥴링 타임 설정값을 지정합니다.


DB 등록.

간단하게 작성된 모델 변수를 DB에 등록합니다.

샘플 모델을 0~1사이의 값으로 값을 생성하여 타이밍 스케쥴러에 따라서 연산을 수행합니다.


화면 픽쳐 객체 생성.

페이지 로딩시 2D 객체를 루아 스크립트를 이용하여 동적으로 생성합니다. 생성된 객체에 데이터 바인딩을 수행할 스크립트도 같이 등록하여 줍니다.

function _onload()

local tx = 300

local ty = 200

local i = 1

local j = 1

local k = 1

local id_text

for i=1,24,1 do 

for j=1,15,1 do

id_text = string.format("ID_%d_1_%d_H", j,i)

CreateRect(id_text, 0, 0, 15, 15, 20*j+tx, 20*i+ty)

end

end


local strScript

for i=1,24,1 do 

for j=1,15,1 do

id_text = string.format("ID_%d_1_%d_H", j, i)

strScript = string.format("function _ontaskview()\nfill=GetValueColor(@CORE.flux_%d_1_%d)\nend", j, i)

RegisterLuaScriptById(id_text, "_ontaskview", strScript)

end

end

local tx = 800

local ty = 300


local k = 2

for i=1,15,1 do 

for j = 1,15,1 do

id_text = string.format("ID_%d_%d_%d", i, j, k)

CreateRect(id_text, 0, 0, 15, 15, i*20+tx, j*20+ty)

end

end

for i=1,15,1 do 

for j = 1,15,1 do

id_text = string.format("ID_%d_%d_%d", i, j, k)

strScript = string.format("function _ontaskview()\nfill=GetValueColor(@CORE.flux_%d_%d_%d)\nend", i, j, k)

RegisterLuaScriptById(id_text, "_ontaskview", strScript)

end

end

end 


페이지 로딩시 3D 객체를 루아 스크립트를 이용하여 동적으로 생성합니다. 생성된 객체에 데이터 바인딩을 수행할 스크립트도 같이 등록하여 줍니다.

function _onload()

local tx = 75

local ty = 75

local tz = 150

local i = 1

local j = 1

local k = 1

local id_text

for i=1,15,1 do 

for j = 1,15,1 do

for k = 1,24,1 do

id_text = string.format("ID_%d_%d_%d", i, j, k)

Create3DBox(id_text, 7, i*10-tx,j*10-ty,tz-k*10)

end

end

end

local strScript

for i=1,15,1 do 

for j = 1,15,1 do

for k = 1,24,1 do

id_text = string.format("ID_%d_%d_%d", i, j, k)

strScript = string.format("function _ontaskview()\ndiffuseColor=GetValueColor(@CORE.flux_%d_%d_%d)\nend", i, j, k)

RegisterLuaScriptById(id_text, "_ontaskview", strScript)

end

end

end

end


function GetValueColor(fValue)


fValue = fValue * 50

local color


if fValue<0 then

color = string.format("rgb(0,0,0)")

elseif fValue>0 and fValue <=5 then

color = string.format("rgb(15,75,165)")

elseif fValue>5 and fValue <=10 then

color = string.format("rgb(30,110,200)")

elseif fValue>10 and fValue <=15 then

color = string.format("rgb(60,160,240)")

elseif fValue>15 and fValue <=20 then

color = string.format("rgb(80,180,250)")

elseif fValue>20 and fValue <=25 then

color = string.format("rgb(130,210,255)")

elseif fValue>25 and fValue <=30 then

color = string.format("rgb(160,240,255)")

elseif fValue>30 and fValue <=35 then

color = string.format("rgb(200,250,255)")

elseif fValue>35 and fValue <=40 then

color = string.format("rgb(230,255,255)")

elseif fValue>40 and fValue <=45 then

color = string.format("rgb(255,250,220)")

elseif fValue>45 and fValue <=50 then

color = string.format("rgb(255,232,120)")

elseif fValue>50 and fValue <=55 then

color = string.format("rgb(255,192,60)")

elseif fValue>55 and fValue <=60 then

color = string.format("rgb(255,160,0)")

elseif fValue>60 and fValue <=65 then

color = string.format("rgb(255,96,0)")

elseif fValue>65 and fValue <=70 then

color = string.format("rgb(255,50,0)")

elseif fValue>70 and fValue <=75 then

color = string.format("rgb(225,20,0)")

elseif fValue>75 and fValue <=80 then

color = string.format("rgb(192,0,0)")

elseif fValue > 80 then

color = string.format("rgb(150,0,0)")

end

return color

end






enuSpace 

평가판 다운로드

<데모 동영상>

본 소프트웨어는 객체 지향형 그래픽 편집 프로그램으로 사용자 정의 라이브러리를 생성하고, 생성된 라이브러리를 활용하여 또다른 라이브러리를 생성할 수 있는 기능을 제공합니다. HMI(Human Machine Interface) 라이브러리를 멤버변수와 멤버함수로 구현합니다. Logic 라이브러리를 개발하여 연결선을 이용한 Flowbase Programming이 가능한 프로그램입니다.  

본 프로그램은 그래픽 컴포넌트, 그래픽 편집, 로직 처리를 하나의 솔루션으로 적용할 수 있습니다. enuSpace 파일 포맷은 SVG(Scalable Vector Graphics)로 구성되었습니다.

스케일러블 벡터 그래픽스(Scalable Vector Graphics, SVG)는 2차원 벡터 그래픽을 표현하기 위한 XML 기반의 파일 형식으로, 1999년 W3C(World Wide Web Consortium)의 주도하에 개발된 오픈 표준의 벡터 그래픽 파일 형식이다. SVG 형식의 이미지와 그 작동은 XML 텍스트 파일들로 정의 됩니다.


ALL IN ONE Graphics Component (그래픽 컴포넌트 하나의 솔루션으로 완벽 해결)

본 소프트웨어의 SDK는 강력한 그래픽 컴포넌트를 제공합니다. 사용자 응용 프로그램 개발에 SDK 라이브러리와 해더파일을 포함하여 개발자의 응용 프로그램을 손쉽게 제작할 수 있습니다. 약 300 여종의 API를 제공하며, 기본 API만의 활용만으로 나만의 수려한 응용 프로그램제작이 가능하다. 그래픽 및 편집기 제작용 응용프로그램 제작에 매우 효율적입니다. 

<작업 동영상>

ALL IN ONE Graphics Editor (강력한 편집기)

본 소프트웨어는 라이브러리 제작 화면 구성을 위한 편집기를 제공합니다. 라이브러리는 HMI와 Logic 심볼로 구성됩니다. HMI는 동적 현시를 위한 목적으로 제작하여 활용할 수 있으며, 제작시 스크립트 언어(Lua)를 기반으로 쉽고 빠르게 제작할 수 있습니다. 

루아(Lua) 프로그래밍 언어는 가벼운 명령형/절차적 언어로, 확장 언어로 쓰일 수 있는 스크립팅 언어를 주 목적으로 설계되었다. 루아는 "달"을 의미하는 포르투갈어 단어입니다.

제작된 라이브러리는 실시간으로 화면 구성에 적용할 수 있습니다. 또한, 기 제작된 라이브러리 파일은 프로젝트 워크스페이스를 통하여 포함하여 재사용할 수 있습니다. 

강력한 Zoom 스케일 기능, 하나의 페이지에 GUI 및 다이어그램을 확대 축소 기능을 통하여 구성할 수 있습니다.  


ALL IN ONE Logic Simulation

본 소프트웨어는 스크립트 기반의 Logic 시뮬레이션이 가능합니다. 사용자가 정의한 Logic 라이브러리를 화면상에 Drag & Drop기능으로 로직 객체를 생성하며, 생성된 객체간의 연결선 연결만으로 시뮬레이션을 시간으로 확인 할 수 있습니다. 또한, 시뮬레이션 진행중에 편집이 가능하며, 실시간 처리 결과를 확인 할 수 있습니다.

분산 모델 시험 장비에 최적의 솔루션을 제공합니다. 손쉽게 각 개별 서버를 이용하여 분산 시뮬레이션 환경을 제공합니다. 

구매 상담 문의 : 070-4244-2268, master@enu-tech.co.kr  (이엔유주식회사)


'enuSpace for moon(2016) > Architecture' 카테고리의 다른 글

enuSpace Runtime Scheduler  (0) 2016.01.04

+ Recent posts