[태그:] raspberry

  • 라즈베리 파이4 온도/습도센서 홈페이지에 연결(그래프 추가)

    프린터 서버로 동작하는 라즈베리 파이에 온도, 습도를 측정하는 dht11 센서를 달았다. 센서는 1,500원인데 배송비 포함 4,000원에 구매했다. 인터넷에서 바로 사용할 수 있는 python 코드를 찾았지만 동작하지 않아 gcc로 된 코드를 사용했다. 아래 그림과 같이 구성했다.

    111에러가 홈 서버 접속 시도를 차단했다. 찾아보니 서버 내 my.cnf 파일 bind 설정을 수정해야 함을 알았다.

    워드 프레스 테마 디렉토리 안 functions.php를 보면 사용자 정의 함수를 만들고, 데이터베이스를 조회할 수 있다. functions.php 파일을 수정하기 보다, header.php를 간단하게 수정했다. 스타일 등 html을 잘 모르기 때문에 가장 간단한 정보만 표시했다. 아래는 header.php 파일이다. 마지막에 4줄 정도만 넣었다.

    <?php
    /**
     * The header for our theme
     *
     * This is the template that displays all of the <head> section and everything up until <div id="content">
     *
     * @link https://developer.wordpress.org/themes/basics/template-files/#template-partials
     *
     * @package WordPress
     * @subpackage Twenty_Seventeen
     * @since Twenty Seventeen 1.0
     * @version 1.0
     */
    
    ?><!DOCTYPE html>
    <html <?php language_attributes(); ?> class="no-js no-svg">
    <head>
    <meta charset="<?php bloginfo( 'charset' ); ?>">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="profile" href="https://gmpg.org/xfn/11">
    
    <?php wp_head(); ?>
    </head>
    
    <body <?php body_class(); ?>>
    <?php wp_body_open(); ?>
    <div id="page" class="site">
    	<a class="skip-link screen-reader-text" href="#content"><?php _e( 'Skip to content', 'twentyseventeen' ); ?></a>
    
    	<header id="masthead" class="site-header" role="banner">
    
    		<?php get_template_part( 'template-parts/header/header', 'image' ); ?>
    
    		<?php if ( has_nav_menu( 'top' ) ) : ?>
    			<div class="navigation-top">
    				<div class="wrap">
    					<?php get_template_part( 'template-parts/navigation/navigation', 'top' ); ?>
    				</div><!-- .wrap -->
    			</div><!-- .navigation-top -->
    		<?php endif; ?>
    
    	</header><!-- #masthead -->
    
    	<?php
    
    	/*
    	 * If a regular post or page, and not the front page, show the featured image.
    	 * Using get_queried_object_id() here since the $post global may not be set before a call to the_post().
    	 */
    	if ( ( is_single() || ( is_page() && ! twentyseventeen_is_frontpage() ) ) && has_post_thumbnail( get_queried_object_id() ) ) :
    		echo '<div class="single-featured-image-header">';
    		echo get_the_post_thumbnail( get_queried_object_id(), 'twentyseventeen-featured-image' );
    		echo '</div><!-- .single-featured-image-header -->';
    	endif;
    	?>
    
    
    	<div class="site-content-contain">
    		<div id="content" class="site-content">
    
    /*여기 추가*/
    <div align="center">
    <?php
    $mydb = new wpdb('????','?????','???','???');$results = $mydb->get_results("SELECT * FROM `dataTemperatureAndHumidity` ORDER BY `dataTemperatureAndHumidity`.`time` DESC limit 1");foreach($results as $result){echo "온도: "; echo $result->temperature; echo ", 습도: "; echo $result->humidity;echo ", 수집시각: ";echo $result->time;}
    ?></div>
    

    다음은 라즈베리안에서 돌아가는 gcc 파일이다. 인터넷 파일 그대로 사용했고, crobtab으로 30분에 한번 실행하도록 했다. 다음 컴파일 할 때 mysql과 wiringPi 옵션을 주어야 한다.

    /* mysql connect and query sample */
    #include <stdio.h>
    #include <stdlib.h>
    #include <mysql.h>
    #include <time.h>
    #include <unistd.h>
    #include <wiringPi.h>
    #include <stdint.h>
    #define MAXTIMINGS 83
    #define DHTPIN 0 
    int dht11_dat[5] = {0, } ;
    
    void read_dht11_dat()
    {
    	uint8_t laststate = HIGH ;
    	uint8_t counter = 0 ;
    	uint8_t j = 0, i ;
    	uint8_t flag = HIGH ;
    	uint8_t state = 0 ;
    	float f ;
    	dht11_dat[0] = dht11_dat[1] = dht11_dat[2] = dht11_dat[3] = dht11_dat[4] = 0 ;
    	pinMode(DHTPIN, OUTPUT) ;
    	digitalWrite(DHTPIN, LOW) ;
    	delay(18) ;
    	digitalWrite(DHTPIN, HIGH) ;
    	delayMicroseconds(30) ;
    	pinMode(DHTPIN, INPUT) ;
    	for (i = 0; i < MAXTIMINGS; i++) {
    		counter = 0 ;
    		while ( digitalRead(DHTPIN) == laststate) {
    			counter++ ;
    			delayMicroseconds(1) ;
    			if (counter == 200) break ;
    		}
    		laststate = digitalRead(DHTPIN) ;
    		if (counter == 200) break ; // if while breaked by timer, break for
    		if ((i >= 4) && (i % 2 == 0)) {
    			dht11_dat[j / 8] <<= 1 ;
    			if (counter > 20) dht11_dat[j / 8] |= 1 ;
    			j++ ;
    		}
    	}
    	if ((j >= 40) && (dht11_dat[4] == ((dht11_dat[0] + dht11_dat[1] + dht11_dat[2] +
    						dht11_dat[3]) & 0xff))) {
    		printf("humidity = %d.%d %% Temperature = %d.%d *C \n", dht11_dat[0],
    				dht11_dat[1], dht11_dat[2], dht11_dat[3]) ;
    	}
    	else printf("Data get failed\n") ;
    }
    
    float RandomFloat(float a, float b) {
    	//seed 값 현재 시각으로 초기화
    	srand(time(NULL));
        float random = ((float) rand()) / (float) RAND_MAX;
    
        float diff = b - a;
        float r = random * diff;
        return a + r;
    }
     
    int main(int argc, char **argv)
    {
    		if (wiringPiSetup() == -1) exit(1) ;
    		char temp[10];
    		char humidity[10];
    
    		read_dht11_dat();
    
    		sprintf(temp, "%d.%d", dht11_dat[2], dht11_dat[3]);
    		sprintf(humidity, "%d.%d",dht11_dat[0], dht11_dat[1]);
    
    		MYSQL mysql;
    		MYSQL *conn;
            MYSQL_RES *result;
            MYSQL_ROW row;
    
    		//printf("now: %d-%d-%d %d:%d:%d\n",tm.tm_year+1900, tm.tm_mon+1, tm.tm_mday,tm.tm_hour,tm.tm_min, tm.tm_sec);
     
            char query_buffer[2048];
    		conn = mysql_init(&mysql);
    
    		//float temper, centerVal;		//온도, 중심값.
    		//centerVal=25;
    		//한글을 사용하기 위해 utf-8로 설정.
    		mysql_options(conn, MYSQL_SET_CHARSET_NAME, "utf8");
    		mysql_options(conn, MYSQL_INIT_COMMAND, "SET NAMES utf8");
    		//18, 19행은 한글 사용하기 위해, mysql  옵션 수정..
    		//아래는 서버 설정에 맞도록 수정..
    		if(!mysql_real_connect(conn, "????", "????", "????", NULL, 3306, NULL, 0)){
    				fprintf(stderr,"error %s", mysql_error(conn));
    				printf("cannot connect");
    				exit(1);
    		}
    		else{
    			//아래는 test_db를 사용하도록 수정..
    				if (mysql_select_db(conn, "????")){
    						printf("cannot use databases");
    						exit(1);
    				}
    		}
    
    			time_t t =time(NULL);
    			struct tm tm = *localtime(&t);
    
    			sprintf(query_buffer, "select * from dataTemperatureAndHumidity");
    			mysql_query(conn, query_buffer);
    			result = mysql_store_result(conn);
    
    			while( (row = mysql_fetch_row(result)) != NULL){
    				printf("row[0],%s, %s, %s",row[0], row[1], row[2]);
    			}
    			sprintf(query_buffer, "INSERT INTO `dataTemperatureAndHumidity`(`time`, `temperature`, `humidity`) VALUES ('%d-%d-%d %d:%d:%d', %s, %s);",tm.tm_year+1900,tm.tm_mon+1,tm.tm_mday,tm.tm_hour,tm.tm_min, tm.tm_sec, temp, humidity);
    
    
    
    			if (mysql_query(conn, query_buffer)){
    					printf("query faild : %s\n", query_buffer);
    					exit(1);
    			}
    
    		mysql_close(conn);
     
    }

    마누라에게 자랑하니, 왜 만들었냐고 한다. 아래 그림과 같이 잘 나온다.

    (업데이트)그래프를 그릴 수도 있다.

    https://stackoverflow.com/questions/32005229/how-do-i-generate-a-graph-in-mysql-and-php-based-on-my-sql-statement-result

    jpgraph 모듈을 설치해야 한다. 아래 사이트에서 jpgraph를 다운로드 받아 적당한 web page에 넣어 준다.

    https://jpgraph.net/doc/

    header.php에 graph를 직접 넣으면 안된다. JpGraph Error: HTTP headers have already been sent 이런 에러가 난다. php가 파일로 변환 후 html이 이미지로 표시하게 한다.

    https://stackoverflow.com/questions/10487796/jpgraph-error-http-headers-have-already-been-sent

    header.php

    <?php
    /**
     * The header for our theme
     *
     * This is the template that displays all of the <head> section and everything up until <div id="content">
     *
     * @link https://developer.wordpress.org/themes/basics/template-files/#template-partials
     *
     * @package WordPress
     * @subpackage Twenty_Seventeen
     * @since Twenty Seventeen 1.0
     * @version 1.0
     */
    
    ?><!DOCTYPE html>
    <html <?php language_attributes(); ?> class="no-js no-svg">
    <head>
    <meta charset="<?php bloginfo( 'charset' ); ?>">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="profile" href="https://gmpg.org/xfn/11">
    
    <?php wp_head(); ?>
    </head>
    
    <body <?php body_class(); ?>>
    <?php wp_body_open(); ?>
    <div id="page" class="site">
    	<a class="skip-link screen-reader-text" href="#content"><?php _e( 'Skip to content', 'twentyseventeen' ); ?></a>
    
    	<header id="masthead" class="site-header" role="banner">
    
    		<?php get_template_part( 'template-parts/header/header', 'image' ); ?>
    
    		<?php if ( has_nav_menu( 'top' ) ) : ?>
    			<div class="navigation-top">
    				<div class="wrap">
    					<?php get_template_part( 'template-parts/navigation/navigation', 'top' ); ?>
    				</div><!-- .wrap -->
    			</div><!-- .navigation-top -->
    		<?php endif; ?>
    
    	</header><!-- #masthead -->
    
    	<?php
    
    	/*
    	 * If a regular post or page, and not the front page, show the featured image.
    	 * Using get_queried_object_id() here since the $post global may not be set before a call to the_post().
    	 */
    	if ( ( is_single() || ( is_page() && ! twentyseventeen_is_frontpage() ) ) && has_post_thumbnail( get_queried_object_id() ) ) :
    		echo '<div class="single-featured-image-header">';
    		echo get_the_post_thumbnail( get_queried_object_id(), 'twentyseventeen-featured-image' );
    		echo '</div><!-- .single-featured-image-header -->';
    	endif;
    	?>
    
    
    	<div class="site-content-contain">
    		<div id="content" class="site-content">
    
    <div align="center">
    <?php
    
    
    		$ydata = array();
    		$y2data = array();
    		$xdata = array();
    		$mydb = new wpdb('???','???','???','???');
    		$results = $mydb->get_results("SELECT * FROM `dataTemperatureAndHumidity` ORDER BY `dataTemperatureAndHumidity`.`time` DESC limit 30");
    		foreach($results as $result){
    			$ydata[] = $result->temperature;
    			$y2data[] = $result->humidity;
    			$xdata[] = strtotime($result->time);
    			#echo $result->time;
    		};
    		#echo gettype($xdata[0]);
    
    // Create graph instance
    require_once ('jpgraph/src/jpgraph.php');
    require_once ('jpgraph/src/jpgraph_line.php');
    require_once ('jpgraph/src/jpgraph_date.php');
    
    // Some (random) data
    #$ydata = array(11,3,8,12,5,1,9,13,5,7);
    
    // Size of the overall graph
    $width=400;
    $height=100;
    
    // Create the graph and set a scale.
    // These two calls are always required
    $graph = new Graph($width,$height);
    $graph->SetScale('datlin');
    $graph->title->Set("Temperature");
    
    $graph2 = new Graph($width,$height);
    $graph2->SetScale('datlin');
    $graph2->title->Set("Humidity");
    
    // Create the linear plot
    $lineplot=new LinePlot($ydata, $xdata);
    $lineplot2=new LinePlot($y2data, $xdata);
    
    // Add the plot to the graph
    $graph->Add($lineplot);
    $graph2->Add($lineplot2);
    
    // Display the graph
    $graph->Stroke("./temperature.jpg");
    $graph2->Stroke("./humidity.jpg");
    
    ?></div>
    <div align = "center"><img src = "./temperature.jpg"/> <img src = "./humidity.jpg" /> </div>
    

    잘 표시된다. 홈 페이지가 갈수록 괴상하게 변한다.

  • open62541 server+client 공정 동작 시간 측정

    처음 설정한 목표를 드디어 수행했다. 서버를 대략 구현했으니 다음으로 클라이언트를 만들었다. 내가 쪼랩이라 tutorial 문서 그대로 사용했다. cilent는 UA_Client_Service_browse로 server가 어떤 데이터를 가지고 있는지 볼 수 있다. 그러나 역시 한 단계밖에 볼 수 없다. 하부 구조를 보려면 nodeId를 기억하여 다시 browse를 해야 한다. 더 좋은 방법이 있겠지만, 이것도 되니까 문제 안된다.

    서버가 method를 가지고 있어 client에서 오는 콜을 받아 정리해서 보내 줄 수도 있지만, 시간을 많이 쓸 듯하여 쉽고 간단하게 갔다.

    서버가 각 공정 동작 상황을 OPC UA nodeId에 기록한다. 5초마다 업데이트 했는데, 실재 PLC로 구현한다면 매 초마다 update하는 방식으로 해야 할 듯하다. 그리 많은 부하가 걸리지는 않을 듯 하다.

    클라이언트는 매 3초마다 서버로 request하여 정보를 받아오는 방식으로 작성했다. timestamp는 server쪽 시각을 쓰지 않고, 클라이언트가 임의로 만든 시각을 기록했다.

    데이터를 데이터 베이스로 바로 업데이트 한다던가, 파일로 기록할 수도 있다. 그러나 시간을 많이 쓰므로 표준 출력으로 나온 메세지를 file로 redirect하면 쉽게 할 수 있다. 중복 데이터는 sort로 쉽게 지울 수 있다. bash 만세!

    #include "open62541.h"
    
    #include <signal.h>
    #include <stdlib.h>
    #include <time.h>
    
    static volatile UA_Boolean running = true;
    static void stopHandler(int sig) {
    	UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "received ctrl-c");
    	running = false;
    }
    
    static void
    beforeReadVal(UA_Server *server,
    		const UA_NodeId *sessionId, void *sessionContext,
    		const UA_NodeId *nodeid, void *nodeContext,
    		const UA_NumericRange *range, const UA_DataValue *data) {
    	//updateCurrentTime(server);
    	int tmpVal = rand()%1000;
    	UA_Variant value;
    	UA_Variant_setScalar(&value, &tmpVal, &UA_TYPES[UA_TYPES_INT32]);
    	UA_NodeId currentNodeId = UA_NODEID_STRING(1, "SEQ");
    	//UA_Server_writeValue(server, currentNodeId, value);
    }
    
    /* predefined identifier for later use */
    UA_NodeId pumpTypeId = {1, UA_NODEIDTYPE_NUMERIC, {1001}};
    UA_NodeId cylTypeId = {1, UA_NODEIDTYPE_NUMERIC, {2001}};
    UA_NodeId robotTypeId = {1, UA_NODEIDTYPE_NUMERIC, {3001}};
    
    
    static void
    defineObjectTypes(UA_Server *server) {
    	/* Define the object type for "Device" */
    	UA_NodeId deviceTypeId; /* get the nodeid assigned by the server */
    	UA_ObjectTypeAttributes dtAttr = UA_ObjectTypeAttributes_default;
    	dtAttr.displayName = UA_LOCALIZEDTEXT("en-US", "DeviceType");
    	UA_Server_addObjectTypeNode(server, UA_NODEID_NULL,
    			UA_NODEID_NUMERIC(0, UA_NS0ID_BASEOBJECTTYPE),
    			UA_NODEID_NUMERIC(0, UA_NS0ID_HASSUBTYPE),
    			UA_QUALIFIEDNAME(1, "DeviceType"), dtAttr,
    			NULL, &deviceTypeId);
    
    	UA_VariableAttributes mnAttr = UA_VariableAttributes_default;
    	mnAttr.displayName = UA_LOCALIZEDTEXT("en-US", "ManufacturerName");
    	UA_NodeId manufacturerNameId;
    	UA_Server_addVariableNode(server, UA_NODEID_NULL, deviceTypeId,
    			UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT),
    			UA_QUALIFIEDNAME(1, "ManufacturerName"),
    			UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE), mnAttr, NULL, &manufacturerNameId);
    	/* Make the manufacturer name mandatory */
    	UA_Server_addReference(server, manufacturerNameId,
    			UA_NODEID_NUMERIC(0, UA_NS0ID_HASMODELLINGRULE),
    			UA_EXPANDEDNODEID_NUMERIC(0, UA_NS0ID_MODELLINGRULE_MANDATORY), true);
    
    
    	UA_VariableAttributes modelAttr = UA_VariableAttributes_default;
    	modelAttr.displayName = UA_LOCALIZEDTEXT("en-US", "ModelName");
    	UA_Server_addVariableNode(server, UA_NODEID_NULL, deviceTypeId,
    			UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT),
    			UA_QUALIFIEDNAME(1, "ModelName"),
    			UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE), modelAttr, NULL, NULL);
    
    	/* Define the object type for "Pump" */
    	UA_ObjectTypeAttributes ptAttr = UA_ObjectTypeAttributes_default;
    	ptAttr.displayName = UA_LOCALIZEDTEXT("en-US", "PumpType");
    	UA_Server_addObjectTypeNode(server, pumpTypeId,
    			deviceTypeId, UA_NODEID_NUMERIC(0, UA_NS0ID_HASSUBTYPE),
    			UA_QUALIFIEDNAME(1, "PumpType"), ptAttr,
    			NULL, NULL);
    
    	UA_VariableAttributes statusAttr = UA_VariableAttributes_default;
    	statusAttr.displayName = UA_LOCALIZEDTEXT("en-US", "Status");
    	statusAttr.valueRank = UA_VALUERANK_SCALAR;
    	UA_NodeId statusId;
    	UA_Server_addVariableNode(server, UA_NODEID_NULL, pumpTypeId,
    			UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT),
    			UA_QUALIFIEDNAME(1, "Status"),
    			UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE), statusAttr, NULL, &statusId);
    	/* Make the status variable mandatory */
    	UA_Server_addReference(server, statusId,
    			UA_NODEID_NUMERIC(0, UA_NS0ID_HASMODELLINGRULE),
    			UA_EXPANDEDNODEID_NUMERIC(0, UA_NS0ID_MODELLINGRULE_MANDATORY), true);
    
    	UA_VariableAttributes rpmAttr = UA_VariableAttributes_default;
    	rpmAttr.displayName = UA_LOCALIZEDTEXT("en-US", "MotorRPM");
    	rpmAttr.valueRank = UA_VALUERANK_SCALAR;
    	UA_Server_addVariableNode(server, UA_NODEID_NULL, pumpTypeId,
    			UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT),
    			UA_QUALIFIEDNAME(1, "MotorRPMs"),
    			UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE), rpmAttr, NULL, NULL);
    }
    
    static void
    defineCylTypes(UA_Server *server) {
    	/* Define the object type for "Device" */
    	UA_NodeId deviceTypeId; /* get the nodeid assigned by the server */
    	UA_ObjectTypeAttributes dtAttr = UA_ObjectTypeAttributes_default;
    	dtAttr.displayName = UA_LOCALIZEDTEXT("en-US", "Cylinder Type");
    	UA_Server_addObjectTypeNode(server, UA_NODEID_NULL,
    			UA_NODEID_NUMERIC(0, UA_NS0ID_BASEOBJECTTYPE),
    			UA_NODEID_NUMERIC(0, UA_NS0ID_HASSUBTYPE),
    			UA_QUALIFIEDNAME(1, "Cylinder Type"), dtAttr,
    			NULL, &deviceTypeId);
    
    	UA_VariableAttributes mnAttr = UA_VariableAttributes_default;
    	mnAttr.displayName = UA_LOCALIZEDTEXT("en-US", "Manufacturer Name");
    	UA_NodeId manufacturerNameId;
    	UA_Server_addVariableNode(server, UA_NODEID_NULL, deviceTypeId,
    			UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT),
    			UA_QUALIFIEDNAME(1, "Cylinder"),
    			UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE), mnAttr, NULL, &manufacturerNameId);
    
    	UA_VariableAttributes modelAttr = UA_VariableAttributes_default;
    	UA_NodeId modelNameId;
    	modelAttr.displayName = UA_LOCALIZEDTEXT("en-US", "Cyl Model Name");
    	UA_Server_addVariableNode(server, UA_NODEID_NULL, deviceTypeId,
    			UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT),
    			UA_QUALIFIEDNAME(1, "Cyl Model Name"),
    			UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE), modelAttr, NULL, &modelNameId);
    
    	UA_Server_addReference(server,modelNameId,
    			UA_NODEID_NUMERIC(0, UA_NS0ID_HASMODELLINGRULE),
    			UA_EXPANDEDNODEID_NUMERIC(0, UA_NS0ID_MODELLINGRULE_MANDATORY), true);
    
    	/* Define the object type for "Pump" */
    	UA_ObjectTypeAttributes ptAttr = UA_ObjectTypeAttributes_default;
    	ptAttr.displayName = UA_LOCALIZEDTEXT("en-US", "Running State");
    	UA_Server_addObjectTypeNode(server, cylTypeId,
    			deviceTypeId, UA_NODEID_NUMERIC(0, UA_NS0ID_HASSUBTYPE),
    			UA_QUALIFIEDNAME(1, "Running State"), ptAttr,
    			NULL, NULL);
    
    
    	UA_VariableAttributes statusAttr = UA_VariableAttributes_default;
    	statusAttr.displayName = UA_LOCALIZEDTEXT("en-US", "Status");
    	statusAttr.valueRank = UA_VALUERANK_SCALAR;
    	UA_NodeId statusId;
    	UA_Server_addVariableNode(server, UA_NODEID_NULL, cylTypeId,
    			UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT),
    			UA_QUALIFIEDNAME(1, "Status"),
    			UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE), statusAttr, NULL, &statusId);
    	/* Make the manufacturer name mandatory */
    	// UA_Server_addReference()를 어떻게 하냐에 따라  statusId
    	// instance 만들경우 표시
    	UA_Server_addReference(server, statusId,
    			UA_NODEID_NUMERIC(0, UA_NS0ID_HASMODELLINGRULE),
    			UA_EXPANDEDNODEID_NUMERIC(0, UA_NS0ID_MODELLINGRULE_MANDATORY), true);
    }
    
    
    
    static void
    defineRobotTypes(UA_Server *server) {
    	/* Define the object type for "Device" */
    	UA_NodeId deviceTypeId; /* get the nodeid assigned by the server */
    	UA_ObjectTypeAttributes dtAttr = UA_ObjectTypeAttributes_default;
    	dtAttr.displayName = UA_LOCALIZEDTEXT("en-US", "Robot Type");
    	UA_Server_addObjectTypeNode(server, UA_NODEID_NULL,
    			UA_NODEID_NUMERIC(0, UA_NS0ID_BASEOBJECTTYPE),
    			UA_NODEID_NUMERIC(0, UA_NS0ID_HASSUBTYPE),
    			UA_QUALIFIEDNAME(1, "Robot Type"), dtAttr,
    			NULL, &deviceTypeId);
    
    	UA_VariableAttributes mnAttr = UA_VariableAttributes_default;
    	mnAttr.displayName = UA_LOCALIZEDTEXT("en-US", "Manufacturer Name");
    	UA_NodeId manufacturerNameId;
    	UA_Server_addVariableNode(server, UA_NODEID_NULL, deviceTypeId,
    			UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT),
    			UA_QUALIFIEDNAME(1, "Robot"),
    			UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE), mnAttr, NULL, &manufacturerNameId);
    
    	UA_VariableAttributes modelAttr = UA_VariableAttributes_default;
    	UA_NodeId modelNameId;
    	modelAttr.displayName = UA_LOCALIZEDTEXT("en-US", "Robot Model Name");
    	UA_Server_addVariableNode(server, UA_NODEID_NULL, deviceTypeId,
    			UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT),
    			UA_QUALIFIEDNAME(1, "Robot Model Name"),
    			UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE), modelAttr, NULL, &modelNameId);
    
    	UA_Server_addReference(server,modelNameId,
    			UA_NODEID_NUMERIC(0, UA_NS0ID_HASMODELLINGRULE),
    			UA_EXPANDEDNODEID_NUMERIC(0, UA_NS0ID_MODELLINGRULE_MANDATORY), true);
    
    	/* Define the object type for "Pump" */
    	UA_ObjectTypeAttributes ptAttr = UA_ObjectTypeAttributes_default;
    	ptAttr.displayName = UA_LOCALIZEDTEXT("en-US", "Interlock");
    	UA_Server_addObjectTypeNode(server, robotTypeId,
    			deviceTypeId, UA_NODEID_NUMERIC(0, UA_NS0ID_HASSUBTYPE),
    			UA_QUALIFIEDNAME(1, "Interlock"), ptAttr,
    			NULL, NULL);
    
    
    	UA_VariableAttributes statusAttr = UA_VariableAttributes_default;
    	statusAttr.displayName = UA_LOCALIZEDTEXT("en-US", "Status");
    	statusAttr.valueRank = UA_VALUERANK_SCALAR;
    	UA_NodeId statusId;
    	UA_Server_addVariableNode(server, UA_NODEID_NULL, robotTypeId,
    			UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT),
    			UA_QUALIFIEDNAME(1, "Status"),
    			UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE), statusAttr, NULL, &statusId);
    	/* Make the manufacturer name mandatory */
    	// UA_Server_addReference()를 어떻게 하냐에 따라  statusId
    	// instance 만들경우 표시
    	UA_Server_addReference(server, statusId,
    			UA_NODEID_NUMERIC(0, UA_NS0ID_HASMODELLINGRULE),
    			UA_EXPANDEDNODEID_NUMERIC(0, UA_NS0ID_MODELLINGRULE_MANDATORY), true);
    }
    
    
    
    static void
    addPumpObjectInstance(UA_Server *server, char *name) {
    	UA_ObjectAttributes oAttr = UA_ObjectAttributes_default;
    	oAttr.displayName = UA_LOCALIZEDTEXT("en-US", name);
    	UA_Server_addObjectNode(server, UA_NODEID_NULL,
    			UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER),
    			UA_NODEID_NUMERIC(0, UA_NS0ID_ORGANIZES),
    			UA_QUALIFIEDNAME(1, name),
    			pumpTypeId, /* this refers to the object type
    						   identifier */
    			oAttr, NULL, NULL);
    }
    
    static void
    addCylObjectInstance(UA_Server *server, char *name) {
    	UA_ObjectAttributes oAttr = UA_ObjectAttributes_default;
    	oAttr.displayName = UA_LOCALIZEDTEXT("en-US", name);
    	/*
    	   UA_Server_addObjectNode(UA_Server *server, const UA_NodeId requestedNewNodeId,
    	   const UA_NodeId parentNodeId,
    	   const UA_NodeId referenceTypeId,
    	   const UA_QualifiedName browseName,
    	   const UA_NodeId typeDefinition,
    	   const UA_ObjectAttributes attr,
    	   void *nodeContext, UA_NodeId *outNewNodeId) {
    	 */
    
    	UA_Server_addObjectNode(server, UA_NODEID_NULL,
    			UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER),
    			UA_NODEID_NUMERIC(0, UA_NS0ID_ORGANIZES),
    			UA_QUALIFIEDNAME(1, name),
    			cylTypeId, /* this refers to the object type
    						  identifier */
    			oAttr, NULL, NULL);
    }
    
    
    
    static void
    addRbtObjectInstance(UA_Server *server, char *name) {
    	UA_ObjectAttributes oAttr = UA_ObjectAttributes_default;
    	oAttr.displayName = UA_LOCALIZEDTEXT("en-US", name);
    	/*
    	   UA_Server_addObjectNode(UA_Server *server, const UA_NodeId requestedNewNodeId,
    	   const UA_NodeId parentNodeId,
    	   const UA_NodeId referenceTypeId,
    	   const UA_QualifiedName browseName,
    	   const UA_NodeId typeDefinition,
    	   const UA_ObjectAttributes attr,
    	   void *nodeContext, UA_NodeId *outNewNodeId) {
    	 */
    
    	UA_Server_addObjectNode(server, UA_NODEID_NULL,
    			UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER),
    			UA_NODEID_NUMERIC(0, UA_NS0ID_ORGANIZES),
    			UA_QUALIFIEDNAME(1, name),
    			robotTypeId, /* this refers to the object type
    							identifier */
    			oAttr, NULL, NULL);
    }
    
    
    static UA_StatusCode
    pumpTypeConstructor(UA_Server *server,
    		const UA_NodeId *sessionId, void *sessionContext,
    		const UA_NodeId *typeId, void *typeContext,
    		const UA_NodeId *nodeId, void **nodeContext) {
    	UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "New pump created");
    
    	/* Find the NodeId of the status child variable */
    	UA_RelativePathElement rpe;
    	UA_RelativePathElement_init(&rpe);
    	rpe.referenceTypeId = UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT);
    	rpe.isInverse = false;
    	rpe.includeSubtypes = false;
    	rpe.targetName = UA_QUALIFIEDNAME(1, "Status");
    
    	UA_BrowsePath bp;
    	UA_BrowsePath_init(&bp);
    	bp.startingNode = *nodeId;
    	bp.relativePath.elementsSize = 1;
    	bp.relativePath.elements = &rpe;
    
    	UA_BrowsePathResult bpr =
    		UA_Server_translateBrowsePathToNodeIds(server, &bp);
    	if(bpr.statusCode != UA_STATUSCODE_GOOD ||
    			bpr.targetsSize < 1)
    		return bpr.statusCode;
    
    	/* Set the status value */
    	UA_Boolean status = true;
    	UA_Variant value;
    	UA_Variant_setScalar(&value, &status, &UA_TYPES[UA_TYPES_BOOLEAN]);
    	UA_Server_writeValue(server, bpr.targets[0].targetId.nodeId, value);
    	UA_BrowsePathResult_clear(&bpr);
    
    	/* At this point we could replace the node context .. */
    
    	return UA_STATUSCODE_GOOD;
    }
    
    static void
    addPumpTypeConstructor(UA_Server *server) {
    	UA_NodeTypeLifecycle lifecycle;
    	lifecycle.constructor = pumpTypeConstructor;
    	lifecycle.destructor = NULL;
    	UA_Server_setNodeTypeLifecycle(server, pumpTypeId, lifecycle);
    }
    
    
    
    
    //method 추가
    static UA_StatusCode
    helloWorldMethodCallback(UA_Server *server,
    		const UA_NodeId *sessionId, void *sessionHandle,
    		const UA_NodeId *methodId, void *methodContext,
    		const UA_NodeId *objectId, void *objectContext,
    		size_t inputSize, const UA_Variant *input,
    		size_t outputSize, UA_Variant *output) {
    	UA_String *inputStr = (UA_String*)input->data;
    	UA_String tmp = UA_STRING_ALLOC("Hello ");
    	if(inputStr->length > 0) {
    		tmp.data = (UA_Byte *)UA_realloc(tmp.data, tmp.length + inputStr->length);
    		memcpy(&tmp.data[tmp.length], inputStr->data, inputStr->length);
    		tmp.length += inputStr->length;
    	}
    	UA_Variant_setScalarCopy(output, &tmp, &UA_TYPES[UA_TYPES_STRING]);
    	UA_String_clear(&tmp);
    	UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "Hello World was called");
    	return UA_STATUSCODE_GOOD;
    }
    
    static void
    addHellWorldMethod(UA_Server *server) {
    	UA_Argument inputArgument;
    	UA_Argument_init(&inputArgument);
    	inputArgument.description = UA_LOCALIZEDTEXT("en-US", "A String");
    	inputArgument.name = UA_STRING("MyInput");
    	inputArgument.dataType = UA_TYPES[UA_TYPES_STRING].typeId;
    	inputArgument.valueRank = UA_VALUERANK_SCALAR;
    
    	UA_Argument outputArgument;
    	UA_Argument_init(&outputArgument);
    	outputArgument.description = UA_LOCALIZEDTEXT("en-US", "A String");
    	outputArgument.name = UA_STRING("MyOutput");
    	outputArgument.dataType = UA_TYPES[UA_TYPES_STRING].typeId;
    	outputArgument.valueRank = UA_VALUERANK_SCALAR;
    
    	UA_MethodAttributes helloAttr = UA_MethodAttributes_default;
    	helloAttr.description = UA_LOCALIZEDTEXT("en-US","Say `Hello World`");
    	helloAttr.displayName = UA_LOCALIZEDTEXT("en-US","Hello World");
    	helloAttr.executable = true;
    	helloAttr.userExecutable = true;
    	UA_Server_addMethodNode(server, UA_NODEID_NUMERIC(1,62541),
    			UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER),
    			UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT),
    			UA_QUALIFIEDNAME(1, "hello world"),
    			helloAttr, &helloWorldMethodCallback,
    			1, &inputArgument, 1, &outputArgument, NULL, NULL);
    }
    
    
    //이름으로 nodeId를 찾는 함수.
    UA_StatusCode
    findNodeIdWithName(UA_Server *server, UA_NodeId *startNodeId, char *name, UA_NodeId **retNodeId){
    
    	/* Find the NodeId of the status child variable */
    	UA_RelativePathElement rpe;
    	UA_RelativePathElement_init(&rpe);
    	rpe.referenceTypeId = UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT);
    	rpe.isInverse = false;
    	rpe.includeSubtypes = false;
    	rpe.targetName = UA_QUALIFIEDNAME(1,name); 
    
    	UA_BrowsePath bp;
    	UA_BrowsePath_init(&bp);
    
    
    	bp.startingNode = *startNodeId;
    	bp.relativePath.elementsSize = 1;
    	bp.relativePath.elements = &rpe;
    
    	UA_BrowsePathResult bpr =
    		UA_Server_translateBrowsePathToNodeIds(server, &bp);
    	if(bpr.statusCode != UA_STATUSCODE_GOOD ||
    			bpr.targetsSize < 1)
    		return bpr.statusCode;
    	UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "found target is %d", bpr.targets->targetId.nodeId.identifier.numeric);
    	**retNodeId = bpr.targets->targetId.nodeId;
    
    }
    
    
    
    int main(void) {
    	signal(SIGINT, stopHandler);
    	signal(SIGTERM, stopHandler);
    
    	UA_Server *server = UA_Server_new();
    	UA_ServerConfig_setDefault(UA_Server_getConfig(server));
    	UA_ServerConfig* config = UA_Server_getConfig(server);
    	config->verifyRequestTimestamp = UA_RULEHANDLING_ACCEPT;
    
    	//addVariable(server);
    	//writeVariable(server);
    	//writeWrongVariable(server);
    
    
    	//vendor, serial, variable 순으로 추가.
    
    	//공통으로 사용할 부분 설정.
    	UA_NodeId parentNodeId = UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER);
    	UA_NodeId parentReferenceNodeId = UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT);
    	//variable 추가.
    	UA_VariableAttributes varAttr = UA_VariableAttributes_default;
    	UA_Int32 varName = 10;
    	UA_Variant_setScalar(&varAttr.value, &varName, &UA_TYPES[UA_TYPES_INT32]);
    	UA_NodeId varNodeId = UA_NODEID_STRING(1, "SEQ");
    	UA_QualifiedName myVarName = UA_QUALIFIEDNAME(1, "SEQ");
    	UA_Server_addVariableNode(server, varNodeId, parentNodeId,
    			parentReferenceNodeId, myVarName,
    			UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE), varAttr, NULL, NULL);
    
    	//add callback
    	UA_ValueCallback callback ;
    	callback.onRead = beforeReadVal;
    	callback.onWrite = NULL;
    	UA_NodeId currentNodeId = UA_NODEID_STRING(1, "SEQ");
    	UA_Server_setVariableNode_valueCallback(server, currentNodeId, callback);
    
    	//설비 정의
    
    	defineCylTypes(server);
    	addCylObjectInstance(server, "pin");
    	addCylObjectInstance(server, "latch");
    	addCylObjectInstance(server, "clamp");
    	defineRobotTypes(server);
    	addRbtObjectInstance(server, "Loading Robot");
    
    	/*
    	   defineObjectTypes(server);
    	   addPumpTypeConstructor(server);
    	   addPumpObjectInstance(server, "pump4");
    	 */
    
    
    	//method
    	addHellWorldMethod(server);
    
    
    	//ladder를 시뮬래이션하는 부분.
    	//thread로 latch fwd -> clamp fwd -> pin fwd -> robot in -> pin bwd: 공정 작업시간
    	//         robot in off -> clamp bwd -> latch bwd 순으로 실행
    
    #undef TESe
    #ifdef TEST
    
    	//테스트
    	//각 설비값을 설정하는 부분
    	UA_Variant tmp_value;
    	bool bitoff = 0;
    	UA_Variant_setScalar(&tmp_value, &bitoff, &UA_TYPES[UA_TYPES_BOOLEAN]);
    	UA_NodeId tmpNodeId = UA_NODEID_STRING(1, "Status");
    	UA_Server_writeValue(server, tmpNodeId, tmp_value);
    
    	/* Find the NodeId of the status child variable */
    	UA_RelativePathElement rpe;
    	UA_RelativePathElement_init(&rpe);
    	rpe.referenceTypeId = UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT);
    	rpe.isInverse = false;
    	rpe.includeSubtypes = true;
    	//rpe.targetName = UA_QUALIFIEDNAME(1, "Status");
    	rpe.targetName = UA_QUALIFIEDNAME(1, "latch");
    	//rpe.targetName = UA_QUALIFIEDNAME(1, "variable");
    
    
    	UA_BrowsePath bp;
    	UA_BrowsePath_init(&bp);
    	UA_NodeId *nodeId  = UA_NodeId_new();
    	*nodeId = UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER);
    	//UA_NodeId tmp = {0, UA_NODEIDTYPE_NUMERIC, {50238}};
    	//printf("\nNode id is %d\n", tmp);
    	//UA_NodeId *nodeId = &tmp;
    	bp.startingNode = *nodeId;
    	bp.relativePath.elementsSize = 1;
    	bp.relativePath.elements = &rpe;
    
    	UA_BrowsePathResult bpr =
    		UA_Server_translateBrowsePathToNodeIds(server, &bp);
    	if(bpr.statusCode != UA_STATUSCODE_GOOD ||
    			bpr.targetsSize < 1)
    		return bpr.statusCode;
    
    	UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "found target is %d", bpr.targets->targetId.nodeId.identifier.numeric);
    	//print로 structure를 출력할 수 없음.
    	//printf("\nnode id is %d\n", (UA_NodeId)bpr.targets->targetId.nodeId);
    	//* Set the status value */
    	UA_Boolean status = true;
    	UA_Variant value;
    	UA_Variant_setScalar(&value, &status, &UA_TYPES[UA_TYPES_BOOLEAN]);
    	UA_Server_writeValue(server, bpr.targets->targetId.nodeId, value);
    	//UA_Server_writeValue(server, tmp, value);
    
    	//다시 아래로 내림.
    	bp.startingNode = bpr.targets->targetId.nodeId;
    	rpe.targetName = UA_QUALIFIEDNAME(1, "Status");
    	UA_BrowsePathResult bpr2 =
    		UA_Server_translateBrowsePathToNodeIds(server, &bp);
    	if(bpr2.statusCode != UA_STATUSCODE_GOOD ||
    			bpr2.targetsSize < 1)
    		return bpr.statusCode;
    
    	UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "found target is %d", bpr2.targets->targetId.nodeId.identifier.numeric);
    	UA_Server_writeValue(server, bpr2.targets->targetId.nodeId, value);
    	UA_BrowsePathResult_clear(&bpr);
    #endif
    
    	//NodeId를 저장할 structure 생성
    	struct eqiupNodeId{
    		UA_NodeId latchNodeId;
    		UA_NodeId clampNodeId;
    		UA_NodeId pinNodeId;
    		UA_NodeId robotNodeId;
    	};
    
    	struct eqiupNodeId myEquipNodeId;
    
    	UA_NodeId **nodeIdtmp;
    	UA_NodeId *nodeIdtmp2 = UA_NodeId_new();
    	nodeIdtmp = &nodeIdtmp2;
    	UA_NodeId *nodeId  = UA_NodeId_new();
    	*nodeId = UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER);
    
    	if(	findNodeIdWithName(server, nodeId, "latch", nodeIdtmp) == UA_STATUSCODE_GOOD){
    		nodeId = *nodeIdtmp;
    		if( findNodeIdWithName(server, nodeId, "Status", nodeIdtmp) == UA_STATUSCODE_GOOD){
    			nodeId = *nodeIdtmp;
    			myEquipNodeId.latchNodeId = *nodeId;
    			//printf("Variable 변경\n");
    			//* Set the status value */
    			//			UA_Boolean status = false;
    			//			UA_Variant value;
    			//			UA_Variant_setScalar(&value, &status, &UA_TYPES[UA_TYPES_BOOLEAN]);
    			//			UA_Server_writeValue(server, *nodeId, value);
    			//			UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "node id %d value was written", nodeId->identifier.numeric);
    		}
    	}
    	*nodeId = UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER);
    	if(	findNodeIdWithName(server, nodeId, "clamp", nodeIdtmp) == UA_STATUSCODE_GOOD){
    		nodeId = *nodeIdtmp;
    		if( findNodeIdWithName(server, nodeId, "Status", nodeIdtmp) == UA_STATUSCODE_GOOD){
    			nodeId = *nodeIdtmp;
    			myEquipNodeId.clampNodeId = *nodeId;
    
    			//printf("Variable 변경\n");
    			//* Set the status value */
    			//			UA_Boolean status = false;
    			//			UA_Variant value;
    			//			UA_Variant_setScalar(&value, &status, &UA_TYPES[UA_TYPES_BOOLEAN]);
    			//			UA_Server_writeValue(server, *nodeId, value);
    			//			UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "node id %d value was written", nodeId->identifier.numeric);
    		}
    	}
    
    	*nodeId = UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER);
    	if(	findNodeIdWithName(server, nodeId, "pin", nodeIdtmp) == UA_STATUSCODE_GOOD){
    		nodeId = *nodeIdtmp;
    		if( findNodeIdWithName(server, nodeId, "Status", nodeIdtmp) == UA_STATUSCODE_GOOD){
    			nodeId = *nodeIdtmp;
    			myEquipNodeId.pinNodeId = *nodeId;
    			//printf("Variable 변경\n");
    			//* Set the status value */
    			//			UA_Boolean status = false;
    			//			UA_Variant value;
    			//			UA_Variant_setScalar(&value, &status, &UA_TYPES[UA_TYPES_BOOLEAN]);
    			//			UA_Server_writeValue(server, *nodeId, value);
    			//			UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "node id %d value was written", nodeId->identifier.numeric);
    		}
    	}
    
    	*nodeId = UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER);
    	if(	findNodeIdWithName(server, nodeId, "Loading Robot", nodeIdtmp) == UA_STATUSCODE_GOOD){
    		nodeId = *nodeIdtmp;
    		if( findNodeIdWithName(server, nodeId, "Status", nodeIdtmp) == UA_STATUSCODE_GOOD){
    			nodeId = *nodeIdtmp;
    			myEquipNodeId.robotNodeId = *nodeId;
    			//printf("Variable 변경\n");
    			//* Set the status value */
    			//			UA_Boolean status = false;
    			//			UA_Variant value;
    			//			UA_Variant_setScalar(&value, &status, &UA_TYPES[UA_TYPES_BOOLEAN]);
    			//			UA_Server_writeValue(server, *nodeId, value);
    			//			UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "node id %d value was written", nodeId->identifier.numeric);
    		}
    	}
    
    
    	//구조체 출력.
    	//printf("현재 노드는 %d \n", myEquipNodeId.latchNodeId.identifier.numeric);
    	//printf("현재 노드는 %d \n", myEquipNodeId.clampNodeId.identifier.numeric);
    	//printf("현재 노드는 %d \n", myEquipNodeId.pinNodeId.identifier.numeric);
    	//printf("현재 노드는 %d \n", myEquipNodeId.robotNodeId.identifier.numeric);
    
    	//한번에 업데이트
    	UA_Boolean statusTrue = true;
    	UA_Boolean statusFalse = false;
    	UA_Variant value;
    	UA_Variant_setScalar(&value, &statusFalse, &UA_TYPES[UA_TYPES_BOOLEAN]);
    
    	//초기상태
    	UA_Server_writeValue(server, myEquipNodeId.latchNodeId, value);
    	UA_Server_writeValue(server, myEquipNodeId.clampNodeId, value);
    	UA_Server_writeValue(server, myEquipNodeId.pinNodeId, value);
    	UA_Server_writeValue(server, myEquipNodeId.robotNodeId, value);
    	UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "모든 노드가 초기화(모든 Value가 0) 됨.");
    
    
    
    	//서버 구동.
    	//주기적으로 업데이트로 수정
    	//https://youtu.be/abDnBv5u6bU//
    	UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "Starting server...every 5 secs");
    	UA_StatusCode retval = UA_Server_run_startup(server);
    
    	if(retval != UA_STATUSCODE_GOOD){
    		UA_Server_delete(server);
    		return retval;
    	}
    
    
    	int timestamp = time(0)+5;
    	int equipStates = 0;
    	int seqInt = 0;
    	bool seqFlag=false;		//false: 현재 작업 중, true: 작업완료 다음 작업 시작
    
    	while(running == true){
    		// Handle Server
    		UA_Server_run_iterate(server, true);
    
    
    		//새로운 seq을 할당 받음
    		if(seqFlag == true)
    			seqInt = rand()%1000;
    
    
    		//Update Status Variable
    		if(time(0) > timestamp)
    		{
    			timestamp = time(0) + 5;
    
    			switch(equipStates)
    			{
    				case 0: //초기상태
    					equipStates = 1;
    					//seq 기록.
    					UA_Variant_setScalar(&value, &seqInt, &UA_TYPES[UA_TYPES_INT32]);
    					UA_NodeId currentNodeId = UA_NODEID_STRING(1, "SEQ");
    					UA_Server_writeValue(server, currentNodeId, value);
    					//flag 비활성화
    					seqFlag = false;
    
    					UA_Variant_setScalar(&value, &statusFalse, &UA_TYPES[UA_TYPES_BOOLEAN]);
    
    					UA_Server_writeValue(server, myEquipNodeId.latchNodeId, value);
    					UA_Server_writeValue(server, myEquipNodeId.clampNodeId, value);
    					UA_Server_writeValue(server, myEquipNodeId.pinNodeId, value);
    					UA_Server_writeValue(server, myEquipNodeId.robotNodeId, value);
    					UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "모든 노드가 초기화(모든 Value가 0) 됨.");
    					break;
    
    				case 1: //1단계, latch fwd
    					equipStates = 2;
    					//flag 비활성화
    					seqFlag = false;
    
    					UA_Variant_setScalar(&value, &statusFalse, &UA_TYPES[UA_TYPES_BOOLEAN]);
    
    					UA_Server_writeValue(server, myEquipNodeId.clampNodeId, value);
    					UA_Server_writeValue(server, myEquipNodeId.pinNodeId, value);
    					UA_Server_writeValue(server, myEquipNodeId.robotNodeId, value);
    
    					UA_Variant_setScalar(&value, &statusTrue, &UA_TYPES[UA_TYPES_BOOLEAN]);
    					UA_Server_writeValue(server, myEquipNodeId.latchNodeId, value);
    					UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "1단계 동작 완료..latch 전진");
    					break;
    
    				case 2: //2단계, clamp fwd
    					equipStates = 3;
    					//flag 비활성화
    					seqFlag = false;
    
    					UA_Variant_setScalar(&value, &statusFalse, &UA_TYPES[UA_TYPES_BOOLEAN]);
    					UA_Server_writeValue(server, myEquipNodeId.pinNodeId, value);
    					UA_Server_writeValue(server, myEquipNodeId.robotNodeId, value);
    
    					UA_Variant_setScalar(&value, &statusTrue, &UA_TYPES[UA_TYPES_BOOLEAN]);
    					UA_Server_writeValue(server, myEquipNodeId.latchNodeId, value);
    					UA_Server_writeValue(server, myEquipNodeId.clampNodeId, value);
    					UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "2단계 동작 완료..calmp 전진(latch, clamp 전진상태)");
    					break;
    
    				case 3: //3단계, pin fwd
    					equipStates = 4;
    					//flag 비활성화
    					seqFlag = false;
    
    					UA_Variant_setScalar(&value, &statusFalse, &UA_TYPES[UA_TYPES_BOOLEAN]);
    					UA_Server_writeValue(server, myEquipNodeId.robotNodeId, value);
    
    					UA_Variant_setScalar(&value, &statusTrue, &UA_TYPES[UA_TYPES_BOOLEAN]);
    					UA_Server_writeValue(server, myEquipNodeId.latchNodeId, value);
    					UA_Server_writeValue(server, myEquipNodeId.clampNodeId, value);
    					UA_Server_writeValue(server, myEquipNodeId.pinNodeId, value);
    					UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "3단계 동작 완료..pin 전진(latch, clamp, pin 전진상태)");
    					break;
    
    				case 4: //4단계, robot in
    					equipStates = 5;
    					//flag 비활성화
    					seqFlag = false;
    
    					UA_Variant_setScalar(&value, &statusTrue, &UA_TYPES[UA_TYPES_BOOLEAN]);
    
    					UA_Server_writeValue(server, myEquipNodeId.robotNodeId, value);
    					UA_Server_writeValue(server, myEquipNodeId.latchNodeId, value);
    					UA_Server_writeValue(server, myEquipNodeId.clampNodeId, value);
    					UA_Server_writeValue(server, myEquipNodeId.pinNodeId, value);
    					UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "4단계 동작 완료..robot in (latch, clamp, pin 전진상태, robot 진입)");
    					break;
    
    				case 5: //5단계, robot in
    					//다시 초기상태로 이동
    					equipStates = 0;
    					//flag 활성화
    					seqFlag = true;
    
    					UA_Variant_setScalar(&value, &statusTrue, &UA_TYPES[UA_TYPES_BOOLEAN]);
    
    					UA_Server_writeValue(server, myEquipNodeId.robotNodeId, value);
    					UA_Server_writeValue(server, myEquipNodeId.latchNodeId, value);
    					UA_Server_writeValue(server, myEquipNodeId.clampNodeId, value);
    
    					UA_Variant_setScalar(&value, &statusFalse, &UA_TYPES[UA_TYPES_BOOLEAN]);
    					UA_Server_writeValue(server, myEquipNodeId.pinNodeId, value);
    
    					UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "5단계 동작 완료..pin bwd(latch, clamp, 전진상태, robot 진입, pin 후진)");
    					break;
    
    				default:
    					equipStates = 0;
    					//flag 비활성화
    					seqFlag = false;
    					seqInt = 0;
    
    					break;
    
    			} //switch
    
    
    		}	//if
    
    
    	} //while
    
    	retval = UA_Server_run_shutdown(server);
    	//UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "Starting server...");
    	//UA_StatusCode retval = UA_Server_run(server, &running);
    	//UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "Shutdown server...");
    
    	UA_Server_delete(server);
    	return retval == UA_STATUSCODE_GOOD ? EXIT_SUCCESS : EXIT_FAILURE;
    } // main
    #include "open62541.h"
    #include <time.h>
    #include <stdlib.h>
    
    
    //#ifdef UA_ENABLE_METHODCALLS
    //UA_StatusCode
    //UA_Client_call(UA_Client *client, const UA_NodeId objectId,
    //               const UA_NodeId methodId, size_t inputSize, const UA_Variant *input,
    //               size_t *outputSize, UA_Variant **output);
    //#endif
    
    int main(void) {
        UA_Client *client = UA_Client_new();
        UA_ClientConfig_setDefault(UA_Client_getConfig(client));
        UA_StatusCode retval = UA_Client_connect(client, "opc.tcp://localhost:4840");
    	printf("retval is %d\n",retval);
        if(retval != UA_STATUSCODE_GOOD) {
            UA_Client_delete(client);
            return (int)retval;
        }
    
    
    	//NodeId로 조회한 값을 저장할 structure 생성
    	struct eqiupVal {
    		UA_Int32 seq;
    		UA_NodeId latchNodeId;
    		UA_NodeId clampNodeId;
    		UA_NodeId pinNodeId;
    		UA_NodeId robotNodeId;
    		UA_Boolean latchVal;
    		UA_Boolean clampVal;
    		UA_Boolean pinVal;
    		UA_Boolean robotVal;
    	};
    	struct eqiupVal myEquipVal;
    	time_t now;
    	struct tm *ts;
    
    
    
        /* Read attribute */
        UA_Int32 value2 = 0;
        //printf("\nReading the value of node (1, \"Test Var:Me\"):\n");
        UA_Variant *val = UA_Variant_new();
        retval = UA_Client_readValueAttribute(client, UA_NODEID_STRING(1, "SEQ"), val);
        if(retval == UA_STATUSCODE_GOOD && UA_Variant_isScalar(val) &&
    			val->type == &UA_TYPES[UA_TYPES_INT32]) {
    		value2 = *(UA_Int32*)val->data;
    		//printf("the value is: %i\n", value2);
    		UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,"current seq is %d",value2);
    
    	}
    //#ifdef UA_ENABLE_METHODCALLS
    //UA_StatusCode
    //UA_Client_call(UA_Client *client, const UA_NodeId objectId,
    //               const UA_NodeId methodId, size_t inputSize, const UA_Variant *input,
    //               size_t *outputSize, UA_Variant **output);
    //#endif
    
    
    	//method call
    
    #ifdef UA_ENABLE_METHODCALLS
    	/* Call a remote method */
    	UA_Variant input;
    	UA_String argString = UA_STRING("Hello Server");
    	UA_Variant_init(&input);
    	//UA_Variant_setScalarCopy(&input, &argString, &UA_TYPES[UA_TYPES_STRING]);
    	UA_Variant_setScalarCopy(&input, &argString, &UA_TYPES[UA_TYPES_STRING]);
    	UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "input data is %s\n",((UA_String*)input.data)->data);
    
    	size_t outputSize;
    	UA_Variant *output;
    	retval = UA_Client_call(client, UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER),
    			UA_NODEID_NUMERIC(1, 62541), 1, &input, &outputSize, &output);
    
    	if(retval == UA_STATUSCODE_GOOD) {
    		printf("Method call was successful, and %lu returned values available.\n",
    				(unsigned long)outputSize);
    
    //		if(UA_Variant_hasScalarType(output, &UA_TYPES[UA_TYPES_STRING])) { // if you are paranoid you may also want to check the dimensions
    //			UA_String value4 = *(UA_String *)output->data;
    //			printf("value4 is %s\n",value4);
    //		}
    		UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "output data is %s\n",((UA_String*)output->data)->data);
    		//UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, (UA_String*)output->data);
    
    		//UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "output is %s", value4);
    		UA_Array_delete(output, outputSize, &UA_TYPES[UA_TYPES_VARIANT]);
    	} else {
    		printf("Method call was unsuccessful, and %x returned values available.\n", retval);
    	}
    	UA_Variant_clear(&input);
    #endif
    
        /* Browse some objects */
        printf("Browsing nodes in objects folder:\n");
        UA_BrowseRequest bReq;
        UA_BrowseRequest_init(&bReq);
        bReq.requestedMaxReferencesPerNode = 0;
        bReq.nodesToBrowse = UA_BrowseDescription_new();
        bReq.nodesToBrowseSize = 1;
        bReq.nodesToBrowse[0].nodeId = UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER); /* browse objects folder */
        bReq.nodesToBrowse[0].resultMask = UA_BROWSERESULTMASK_ALL; /* return everything */
        UA_BrowseResponse bResp = UA_Client_Service_browse(client, bReq);
        printf("%-9s %-16s %-16s %-16s\n", "NAMESPACE", "NODEID", "BROWSE NAME", "DISPLAY NAME");
        for(size_t i = 0; i < bResp.resultsSize; ++i) {
            for(size_t j = 0; j < bResp.results[i].referencesSize; ++j) {
                UA_ReferenceDescription *ref = &(bResp.results[i].references[j]);
                if(ref->nodeId.nodeId.identifierType == UA_NODEIDTYPE_NUMERIC) {
                    printf("%-9d %-16d %-16.*s %-16.*s\n", ref->nodeId.nodeId.namespaceIndex,
                           ref->nodeId.nodeId.identifier.numeric, (int)ref->browseName.name.length,
                           ref->browseName.name.data, (int)ref->displayName.text.length,
                           ref->displayName.text.data);
                } else if(ref->nodeId.nodeId.identifierType == UA_NODEIDTYPE_STRING) {
                    printf("%-9d %-16.*s %-16.*s %-16.*s\n", ref->nodeId.nodeId.namespaceIndex,
                           (int)ref->nodeId.nodeId.identifier.string.length,
                           ref->nodeId.nodeId.identifier.string.data,
                           (int)ref->browseName.name.length, ref->browseName.name.data,
                           (int)ref->displayName.text.length, ref->displayName.text.data);
                }
                /* TODO: distinguish further types */
    			//아래 문장으로 client 모든 brwose를 접근할 수 있음.
    			//한 단계 아래만 보임.
    			//UA_BrowseResponse bResp = UA_Client_Service_browse(client, bReq);
    
    
    			// object class 만 찾아 node로 기록.
    			if(ref->nodeClass == UA_NODECLASS_OBJECT &&
    					!strcmp(ref->browseName.name.data, "Loading Robot")){
    				printf("\nNODE CLASS OBJECT\n");
    				//임시로 node ID를 저장
    				myEquipVal.robotNodeId = (UA_NodeId)ref->nodeId.nodeId;
    			}
    			// object class 만 찾아 node로 기록.
    			if(ref->nodeClass == UA_NODECLASS_OBJECT &&
    					!strcmp(ref->browseName.name.data, "pin")){
    				printf("\nNODE CLASS OBJECT\n");
    				//임시로 node ID를 저장
    				myEquipVal.pinNodeId = (UA_NodeId)ref->nodeId.nodeId;
    			}
    
    			if(ref->nodeClass == UA_NODECLASS_OBJECT &&
    					!strcmp(ref->browseName.name.data, "latch")){
    				printf("\nNODE CLASS OBJECT\n");
    				//임시로 node ID를 저장
    				myEquipVal.latchNodeId = (UA_NodeId)ref->nodeId.nodeId;
    			}
    			if(ref->nodeClass == UA_NODECLASS_OBJECT &&
    					!strcmp(ref->browseName.name.data, "clamp")){
    				printf("\nNODE CLASS OBJECT\n");
    				//임시로 node ID를 저장
    				myEquipVal.clampNodeId = (UA_NodeId)ref->nodeId.nodeId;
    			}
    
            }
        }
        UA_BrowseRequest_clear(&bReq);
    	UA_BrowseResponse_clear(&bResp);
    
    	//얻은 데이터로 value값을 찾기 위한 nodeId 확인
    	//BaseDataType을 얻어내야 함
    	UA_BrowseRequest_init(&bReq);
    	bReq.requestedMaxReferencesPerNode = 0;
    	bReq.requestedMaxReferencesPerNode = 0;
        bReq.nodesToBrowse = UA_BrowseDescription_new();
    	bReq.nodesToBrowseSize = 1;
    	bReq.nodesToBrowse[0].nodeId = myEquipVal.robotNodeId; /* browse objects folder */
        bReq.nodesToBrowse[0].resultMask = UA_BROWSERESULTMASK_ALL; /* return everything */
    	//bResp로 다시 query
    	bResp = UA_Client_Service_browse(client, bReq);
    	for(size_t i = 0; i < bResp.resultsSize; ++i) {
    		for(size_t j = 0; j < bResp.results[i].referencesSize; ++j) {
    			UA_ReferenceDescription *ref = &(bResp.results[i].references[j]);
    			//robot
    			if(ref->nodeClass == UA_NODECLASS_VARIABLE &&
    					!strcmp(ref->browseName.name.data, "Status")){
    				printf("found\n");
    				myEquipVal.robotNodeId = (UA_NodeId)ref->nodeId.nodeId;
    			}
    			
    		}
    	}
    
    	bReq.nodesToBrowse[0].nodeId = myEquipVal.latchNodeId; /* browse objects folder */
        bReq.nodesToBrowse[0].resultMask = UA_BROWSERESULTMASK_ALL; /* return everything */
    	//bResp로 다시 query
    	bResp = UA_Client_Service_browse(client, bReq);
    	for(size_t i = 0; i < bResp.resultsSize; ++i) {
    		for(size_t j = 0; j < bResp.results[i].referencesSize; ++j) {
    			UA_ReferenceDescription *ref = &(bResp.results[i].references[j]);
    			//robot
    			if(ref->nodeClass == UA_NODECLASS_VARIABLE &&
    					!strcmp(ref->browseName.name.data, "Status")){
    				printf("found\n");
    				myEquipVal.latchNodeId = (UA_NodeId)ref->nodeId.nodeId;
    			}
    			
    		}
    	}
    	bReq.nodesToBrowse[0].nodeId = myEquipVal.pinNodeId; /* browse objects folder */
        bReq.nodesToBrowse[0].resultMask = UA_BROWSERESULTMASK_ALL; /* return everything */
    	//bResp로 다시 query
    	bResp = UA_Client_Service_browse(client, bReq);
    	for(size_t i = 0; i < bResp.resultsSize; ++i) {
    		for(size_t j = 0; j < bResp.results[i].referencesSize; ++j) {
    			UA_ReferenceDescription *ref = &(bResp.results[i].references[j]);
    			//robot
    			if(ref->nodeClass == UA_NODECLASS_VARIABLE &&
    					!strcmp(ref->browseName.name.data, "Status")){
    				printf("found\n");
    				myEquipVal.pinNodeId = (UA_NodeId)ref->nodeId.nodeId;
    			}
    			
    		}
    	}
    
    	bReq.nodesToBrowse[0].nodeId = myEquipVal.clampNodeId; /* browse objects folder */
        bReq.nodesToBrowse[0].resultMask = UA_BROWSERESULTMASK_ALL; /* return everything */
    	//bResp로 다시 query
    	bResp = UA_Client_Service_browse(client, bReq);
    	for(size_t i = 0; i < bResp.resultsSize; ++i) {
    		for(size_t j = 0; j < bResp.results[i].referencesSize; ++j) {
    			UA_ReferenceDescription *ref = &(bResp.results[i].references[j]);
    			//robot
    			if(ref->nodeClass == UA_NODECLASS_VARIABLE &&
    					!strcmp(ref->browseName.name.data, "Status")){
    				printf("found\n");
    				myEquipVal.clampNodeId = (UA_NodeId)ref->nodeId.nodeId;
    			}
    			
    		}
    	}
        UA_BrowseRequest_clear(&bReq);
    	UA_BrowseResponse_clear(&bResp);
    
    
    	for(int i=0;i<100;i++){
    
        /* Read attribute */
        UA_Int32 value2 = 0;
        //printf("\nReading the value of node (1, \"Test Var:Me\"):\n");
        retval = UA_Client_readValueAttribute(client, UA_NODEID_STRING(1, "SEQ"), val);
        if(retval == UA_STATUSCODE_GOOD && UA_Variant_isScalar(val) &&
    			val->type == &UA_TYPES[UA_TYPES_INT32]) {
    		//value2 = *(UA_Int32*)val->data;
    		//printf("the value is: %i\n", value2);
    		//UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,"current seq is %d",value2);
    		myEquipVal.seq = *(UA_Int32*)val->data;
    
    	}
    
    		//nodeId로 Value를 얻음. 
    		retval = UA_Client_readValueAttribute(client, myEquipVal.pinNodeId , val);
    		if(retval == UA_STATUSCODE_GOOD && UA_Variant_isScalar(val) &&
    				val->type == &UA_TYPES[UA_TYPES_BOOLEAN]) {
    			myEquipVal.pinVal = *(UA_Boolean*)val->data;
    			//UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,"node id 50232(pin) is %d", *(UA_Boolean*)val->data);
    		}
    
    		//nodeId로 Value를 얻음. 
    		retval = UA_Client_readValueAttribute(client, myEquipVal.robotNodeId, val);
    		if(retval == UA_STATUSCODE_GOOD && UA_Variant_isScalar(val) &&
    				val->type == &UA_TYPES[UA_TYPES_BOOLEAN]) {
    			myEquipVal.robotVal = *(UA_Boolean*)val->data;
    			//UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,"node id 50246(robot) is %d", *(UA_Boolean*)val->data);
    		}
    
    		//nodeId로 Value를 얻음. 
    		retval = UA_Client_readValueAttribute(client, myEquipVal.clampNodeId, val);
    		if(retval == UA_STATUSCODE_GOOD && UA_Variant_isScalar(val) &&
    				val->type == &UA_TYPES[UA_TYPES_BOOLEAN]) {
    			myEquipVal.clampVal = *(UA_Boolean*)val->data;
    			//UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,"node id 50238(clamp) is %d", *(UA_Boolean*)val->data);
    		}
    
    		//nodeId로 Value를 얻음. 
    		retval = UA_Client_readValueAttribute(client, myEquipVal.latchNodeId, val);
    		if(retval == UA_STATUSCODE_GOOD && UA_Variant_isScalar(val) &&
    				val->type == &UA_TYPES[UA_TYPES_BOOLEAN]) {
    			myEquipVal.latchVal = *(UA_Boolean*)val->data;
    			//UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,"node id 50235(latch) is %d", *(UA_Boolean*)val->data);
    		}
    
    
    		///printf("myEquipVal is\n");
    		///printf("pin is %d\n", myEquipVal.pinVal);
    		///printf("robot is %d\n", myEquipVal.robotVal);
    		///printf("latch is %d\n", myEquipVal.latchVal);
    		///printf("clamp is %d\n", myEquipVal.clampVal);
    		now = time(NULL);
    		ts = localtime(&now);
    		UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,"%d cylcle read 완료",i);
    		UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,"%d-%d-%d, %d:%d:%d, seq,%d,latch,%d, clamp,%d, pin,%d,robot,%d.",
    				//ts 3개 +3개 숫자.년월일 + 시각:분:초
    				ts->tm_year+1900, ts->tm_mon+1, ts->tm_mday,
    				ts->tm_hour, ts->tm_min, ts->tm_sec,
    				myEquipVal.seq,
    				myEquipVal.latchVal,myEquipVal.clampVal,
    				myEquipVal.pinVal, myEquipVal.robotVal);
    
    		sleep(3);
    	}
    
    
    	UA_Variant_delete(val);
    	/* Clean up */
    	//	UA_Variant_clear(&value);
    	UA_Variant_clear(&value2);
    
    	UA_Client_delete(client); /* Disconnects the client internally */
    	return EXIT_SUCCESS;
    }

  • open62531 server plc 시뮬레이션

    임의 설비에 latch, clamp, pin, robot을 설치했다. 대부분 래더로 동작하고, PLC 메이커가 cpu에 opc ua 서버 기능을 구현하여 주기적으로 plc 메모리 데이터를 server로 업데이트 한다.(고 생각하자.) 집에 PLC가 없고, 아직 이를 제대로 구현할지 모르겠어 대충 짜집기로 만들었다.

    object로 각 설비를 등록하고 Value 변수로 staus를 bool 타입으로 등록했다. OPC UA 문서를 읽지 못해 이렇게 하는게 맞는지 모르겠다. 암튼 4개(pin, latch, clamp, robot) 를 등록하고 순서대로 동작하도록 했다.

    처음에는 thread로 돌려 UA_Server_writeValue(server, myEquipNodeId.pinNodeId, value);로 써 주려했으나, 유투브에 좋은 동영상을 찾아 그대로 따라했다. 서버 중간에 sleep을 넣어 버리면 client 쪽 연결도 응답하지 못한다. timestamp를 찍어 일정 조건을 넘어가면 update 하도록 (따라)했다. 항상 cycle time이 똑같을 수 없으니, random 값으로 조정 해야겠다.

    #include "open62541.h"
    
    #include <signal.h>
    #include <stdlib.h>
    #include <time.h>
    
    static volatile UA_Boolean running = true;
    static void stopHandler(int sig) {
    	UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "received ctrl-c");
    	running = false;
    }
    
    
    
    
    static void
    beforeReadVal(UA_Server *server,
    		const UA_NodeId *sessionId, void *sessionContext,
    		const UA_NodeId *nodeid, void *nodeContext,
    		const UA_NumericRange *range, const UA_DataValue *data) {
    	//updateCurrentTime(server);
    	int tmpVal = rand()%1000;
    	UA_Variant value;
    	UA_Variant_setScalar(&value, &tmpVal, &UA_TYPES[UA_TYPES_INT32]);
    	UA_NodeId currentNodeId = UA_NODEID_STRING(1, "Test Var:Me");
    	//UA_Server_writeValue(server, currentNodeId, value);
    
    
    }
    
    /* predefined identifier for later use */
    UA_NodeId pumpTypeId = {1, UA_NODEIDTYPE_NUMERIC, {1001}};
    UA_NodeId cylTypeId = {1, UA_NODEIDTYPE_NUMERIC, {2001}};
    UA_NodeId robotTypeId = {1, UA_NODEIDTYPE_NUMERIC, {3001}};
    
    
    static void
    defineObjectTypes(UA_Server *server) {
    	/* Define the object type for "Device" */
    	UA_NodeId deviceTypeId; /* get the nodeid assigned by the server */
    	UA_ObjectTypeAttributes dtAttr = UA_ObjectTypeAttributes_default;
    	dtAttr.displayName = UA_LOCALIZEDTEXT("en-US", "DeviceType");
    	UA_Server_addObjectTypeNode(server, UA_NODEID_NULL,
    			UA_NODEID_NUMERIC(0, UA_NS0ID_BASEOBJECTTYPE),
    			UA_NODEID_NUMERIC(0, UA_NS0ID_HASSUBTYPE),
    			UA_QUALIFIEDNAME(1, "DeviceType"), dtAttr,
    			NULL, &deviceTypeId);
    
    	UA_VariableAttributes mnAttr = UA_VariableAttributes_default;
    	mnAttr.displayName = UA_LOCALIZEDTEXT("en-US", "ManufacturerName");
    	UA_NodeId manufacturerNameId;
    	UA_Server_addVariableNode(server, UA_NODEID_NULL, deviceTypeId,
    			UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT),
    			UA_QUALIFIEDNAME(1, "ManufacturerName"),
    			UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE), mnAttr, NULL, &manufacturerNameId);
    	/* Make the manufacturer name mandatory */
    	UA_Server_addReference(server, manufacturerNameId,
    			UA_NODEID_NUMERIC(0, UA_NS0ID_HASMODELLINGRULE),
    			UA_EXPANDEDNODEID_NUMERIC(0, UA_NS0ID_MODELLINGRULE_MANDATORY), true);
    
    
    	UA_VariableAttributes modelAttr = UA_VariableAttributes_default;
    	modelAttr.displayName = UA_LOCALIZEDTEXT("en-US", "ModelName");
    	UA_Server_addVariableNode(server, UA_NODEID_NULL, deviceTypeId,
    			UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT),
    			UA_QUALIFIEDNAME(1, "ModelName"),
    			UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE), modelAttr, NULL, NULL);
    
    	/* Define the object type for "Pump" */
    	UA_ObjectTypeAttributes ptAttr = UA_ObjectTypeAttributes_default;
    	ptAttr.displayName = UA_LOCALIZEDTEXT("en-US", "PumpType");
    	UA_Server_addObjectTypeNode(server, pumpTypeId,
    			deviceTypeId, UA_NODEID_NUMERIC(0, UA_NS0ID_HASSUBTYPE),
    			UA_QUALIFIEDNAME(1, "PumpType"), ptAttr,
    			NULL, NULL);
    
    	UA_VariableAttributes statusAttr = UA_VariableAttributes_default;
    	statusAttr.displayName = UA_LOCALIZEDTEXT("en-US", "Status");
    	statusAttr.valueRank = UA_VALUERANK_SCALAR;
    	UA_NodeId statusId;
    	UA_Server_addVariableNode(server, UA_NODEID_NULL, pumpTypeId,
    			UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT),
    			UA_QUALIFIEDNAME(1, "Status"),
    			UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE), statusAttr, NULL, &statusId);
    	/* Make the status variable mandatory */
    	UA_Server_addReference(server, statusId,
    			UA_NODEID_NUMERIC(0, UA_NS0ID_HASMODELLINGRULE),
    			UA_EXPANDEDNODEID_NUMERIC(0, UA_NS0ID_MODELLINGRULE_MANDATORY), true);
    
    	UA_VariableAttributes rpmAttr = UA_VariableAttributes_default;
    	rpmAttr.displayName = UA_LOCALIZEDTEXT("en-US", "MotorRPM");
    	rpmAttr.valueRank = UA_VALUERANK_SCALAR;
    	UA_Server_addVariableNode(server, UA_NODEID_NULL, pumpTypeId,
    			UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT),
    			UA_QUALIFIEDNAME(1, "MotorRPMs"),
    			UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE), rpmAttr, NULL, NULL);
    }
    
    static void
    defineCylTypes(UA_Server *server) {
    	/* Define the object type for "Device" */
    	UA_NodeId deviceTypeId; /* get the nodeid assigned by the server */
    	UA_ObjectTypeAttributes dtAttr = UA_ObjectTypeAttributes_default;
    	dtAttr.displayName = UA_LOCALIZEDTEXT("en-US", "Cylinder Type");
    	UA_Server_addObjectTypeNode(server, UA_NODEID_NULL,
    			UA_NODEID_NUMERIC(0, UA_NS0ID_BASEOBJECTTYPE),
    			UA_NODEID_NUMERIC(0, UA_NS0ID_HASSUBTYPE),
    			UA_QUALIFIEDNAME(1, "Cylinder Type"), dtAttr,
    			NULL, &deviceTypeId);
    
    	UA_VariableAttributes mnAttr = UA_VariableAttributes_default;
    	mnAttr.displayName = UA_LOCALIZEDTEXT("en-US", "Manufacturer Name");
    	UA_NodeId manufacturerNameId;
    	UA_Server_addVariableNode(server, UA_NODEID_NULL, deviceTypeId,
    			UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT),
    			UA_QUALIFIEDNAME(1, "Cylinder"),
    			UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE), mnAttr, NULL, &manufacturerNameId);
    
    	UA_VariableAttributes modelAttr = UA_VariableAttributes_default;
    	UA_NodeId modelNameId;
    	modelAttr.displayName = UA_LOCALIZEDTEXT("en-US", "Cyl Model Name");
    	UA_Server_addVariableNode(server, UA_NODEID_NULL, deviceTypeId,
    			UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT),
    			UA_QUALIFIEDNAME(1, "Cyl Model Name"),
    			UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE), modelAttr, NULL, &modelNameId);
    
    	UA_Server_addReference(server,modelNameId,
    			UA_NODEID_NUMERIC(0, UA_NS0ID_HASMODELLINGRULE),
    			UA_EXPANDEDNODEID_NUMERIC(0, UA_NS0ID_MODELLINGRULE_MANDATORY), true);
    
    	/* Define the object type for "Pump" */
    	UA_ObjectTypeAttributes ptAttr = UA_ObjectTypeAttributes_default;
    	ptAttr.displayName = UA_LOCALIZEDTEXT("en-US", "Running State");
    	UA_Server_addObjectTypeNode(server, cylTypeId,
    			deviceTypeId, UA_NODEID_NUMERIC(0, UA_NS0ID_HASSUBTYPE),
    			UA_QUALIFIEDNAME(1, "Running State"), ptAttr,
    			NULL, NULL);
    
    
    	UA_VariableAttributes statusAttr = UA_VariableAttributes_default;
    	statusAttr.displayName = UA_LOCALIZEDTEXT("en-US", "Status");
    	statusAttr.valueRank = UA_VALUERANK_SCALAR;
    	UA_NodeId statusId;
    	UA_Server_addVariableNode(server, UA_NODEID_NULL, cylTypeId,
    			UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT),
    			UA_QUALIFIEDNAME(1, "Status"),
    			UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE), statusAttr, NULL, &statusId);
    	/* Make the manufacturer name mandatory */
    	// UA_Server_addReference()를 어떻게 하냐에 따라  statusId
    	// instance 만들경우 표시
    	UA_Server_addReference(server, statusId,
    			UA_NODEID_NUMERIC(0, UA_NS0ID_HASMODELLINGRULE),
    			UA_EXPANDEDNODEID_NUMERIC(0, UA_NS0ID_MODELLINGRULE_MANDATORY), true);
    }
    
    
    
    static void
    defineRobotTypes(UA_Server *server) {
    	/* Define the object type for "Device" */
    	UA_NodeId deviceTypeId; /* get the nodeid assigned by the server */
    	UA_ObjectTypeAttributes dtAttr = UA_ObjectTypeAttributes_default;
    	dtAttr.displayName = UA_LOCALIZEDTEXT("en-US", "Robot Type");
    	UA_Server_addObjectTypeNode(server, UA_NODEID_NULL,
    			UA_NODEID_NUMERIC(0, UA_NS0ID_BASEOBJECTTYPE),
    			UA_NODEID_NUMERIC(0, UA_NS0ID_HASSUBTYPE),
    			UA_QUALIFIEDNAME(1, "Robot Type"), dtAttr,
    			NULL, &deviceTypeId);
    
    	UA_VariableAttributes mnAttr = UA_VariableAttributes_default;
    	mnAttr.displayName = UA_LOCALIZEDTEXT("en-US", "Manufacturer Name");
    	UA_NodeId manufacturerNameId;
    	UA_Server_addVariableNode(server, UA_NODEID_NULL, deviceTypeId,
    			UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT),
    			UA_QUALIFIEDNAME(1, "Robot"),
    			UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE), mnAttr, NULL, &manufacturerNameId);
    
    	UA_VariableAttributes modelAttr = UA_VariableAttributes_default;
    	UA_NodeId modelNameId;
    	modelAttr.displayName = UA_LOCALIZEDTEXT("en-US", "Robot Model Name");
    	UA_Server_addVariableNode(server, UA_NODEID_NULL, deviceTypeId,
    			UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT),
    			UA_QUALIFIEDNAME(1, "Robot Model Name"),
    			UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE), modelAttr, NULL, &modelNameId);
    
    	UA_Server_addReference(server,modelNameId,
    			UA_NODEID_NUMERIC(0, UA_NS0ID_HASMODELLINGRULE),
    			UA_EXPANDEDNODEID_NUMERIC(0, UA_NS0ID_MODELLINGRULE_MANDATORY), true);
    
    	/* Define the object type for "Pump" */
    	UA_ObjectTypeAttributes ptAttr = UA_ObjectTypeAttributes_default;
    	ptAttr.displayName = UA_LOCALIZEDTEXT("en-US", "Interlock");
    	UA_Server_addObjectTypeNode(server, robotTypeId,
    			deviceTypeId, UA_NODEID_NUMERIC(0, UA_NS0ID_HASSUBTYPE),
    			UA_QUALIFIEDNAME(1, "Interlock"), ptAttr,
    			NULL, NULL);
    
    
    	UA_VariableAttributes statusAttr = UA_VariableAttributes_default;
    	statusAttr.displayName = UA_LOCALIZEDTEXT("en-US", "Status");
    	statusAttr.valueRank = UA_VALUERANK_SCALAR;
    	UA_NodeId statusId;
    	UA_Server_addVariableNode(server, UA_NODEID_NULL, robotTypeId,
    			UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT),
    			UA_QUALIFIEDNAME(1, "Status"),
    			UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE), statusAttr, NULL, &statusId);
    	/* Make the manufacturer name mandatory */
    	// UA_Server_addReference()를 어떻게 하냐에 따라  statusId
    	// instance 만들경우 표시
    	UA_Server_addReference(server, statusId,
    			UA_NODEID_NUMERIC(0, UA_NS0ID_HASMODELLINGRULE),
    			UA_EXPANDEDNODEID_NUMERIC(0, UA_NS0ID_MODELLINGRULE_MANDATORY), true);
    }
    
    
    
    static void
    addPumpObjectInstance(UA_Server *server, char *name) {
    	UA_ObjectAttributes oAttr = UA_ObjectAttributes_default;
    	oAttr.displayName = UA_LOCALIZEDTEXT("en-US", name);
    	UA_Server_addObjectNode(server, UA_NODEID_NULL,
    			UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER),
    			UA_NODEID_NUMERIC(0, UA_NS0ID_ORGANIZES),
    			UA_QUALIFIEDNAME(1, name),
    			pumpTypeId, /* this refers to the object type
    						   identifier */
    			oAttr, NULL, NULL);
    }
    
    static void
    addCylObjectInstance(UA_Server *server, char *name) {
    	UA_ObjectAttributes oAttr = UA_ObjectAttributes_default;
    	oAttr.displayName = UA_LOCALIZEDTEXT("en-US", name);
    	/*
    	   UA_Server_addObjectNode(UA_Server *server, const UA_NodeId requestedNewNodeId,
    	   const UA_NodeId parentNodeId,
    	   const UA_NodeId referenceTypeId,
    	   const UA_QualifiedName browseName,
    	   const UA_NodeId typeDefinition,
    	   const UA_ObjectAttributes attr,
    	   void *nodeContext, UA_NodeId *outNewNodeId) {
    	 */
    
    	UA_Server_addObjectNode(server, UA_NODEID_NULL,
    			UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER),
    			UA_NODEID_NUMERIC(0, UA_NS0ID_ORGANIZES),
    			UA_QUALIFIEDNAME(1, name),
    			cylTypeId, /* this refers to the object type
    						  identifier */
    			oAttr, NULL, NULL);
    }
    
    
    
    static void
    addRbtObjectInstance(UA_Server *server, char *name) {
    	UA_ObjectAttributes oAttr = UA_ObjectAttributes_default;
    	oAttr.displayName = UA_LOCALIZEDTEXT("en-US", name);
    	/*
    	   UA_Server_addObjectNode(UA_Server *server, const UA_NodeId requestedNewNodeId,
    	   const UA_NodeId parentNodeId,
    	   const UA_NodeId referenceTypeId,
    	   const UA_QualifiedName browseName,
    	   const UA_NodeId typeDefinition,
    	   const UA_ObjectAttributes attr,
    	   void *nodeContext, UA_NodeId *outNewNodeId) {
    	 */
    
    	UA_Server_addObjectNode(server, UA_NODEID_NULL,
    			UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER),
    			UA_NODEID_NUMERIC(0, UA_NS0ID_ORGANIZES),
    			UA_QUALIFIEDNAME(1, name),
    			robotTypeId, /* this refers to the object type
    							identifier */
    			oAttr, NULL, NULL);
    }
    
    
    static UA_StatusCode
    pumpTypeConstructor(UA_Server *server,
    		const UA_NodeId *sessionId, void *sessionContext,
    		const UA_NodeId *typeId, void *typeContext,
    		const UA_NodeId *nodeId, void **nodeContext) {
    	UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "New pump created");
    
    	/* Find the NodeId of the status child variable */
    	UA_RelativePathElement rpe;
    	UA_RelativePathElement_init(&rpe);
    	rpe.referenceTypeId = UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT);
    	rpe.isInverse = false;
    	rpe.includeSubtypes = false;
    	rpe.targetName = UA_QUALIFIEDNAME(1, "Status");
    
    	UA_BrowsePath bp;
    	UA_BrowsePath_init(&bp);
    	bp.startingNode = *nodeId;
    	bp.relativePath.elementsSize = 1;
    	bp.relativePath.elements = &rpe;
    
    	UA_BrowsePathResult bpr =
    		UA_Server_translateBrowsePathToNodeIds(server, &bp);
    	if(bpr.statusCode != UA_STATUSCODE_GOOD ||
    			bpr.targetsSize < 1)
    		return bpr.statusCode;
    
    	/* Set the status value */
    	UA_Boolean status = true;
    	UA_Variant value;
    	UA_Variant_setScalar(&value, &status, &UA_TYPES[UA_TYPES_BOOLEAN]);
    	UA_Server_writeValue(server, bpr.targets[0].targetId.nodeId, value);
    	UA_BrowsePathResult_clear(&bpr);
    
    	/* At this point we could replace the node context .. */
    
    	return UA_STATUSCODE_GOOD;
    }
    
    static void
    addPumpTypeConstructor(UA_Server *server) {
    	UA_NodeTypeLifecycle lifecycle;
    	lifecycle.constructor = pumpTypeConstructor;
    	lifecycle.destructor = NULL;
    	UA_Server_setNodeTypeLifecycle(server, pumpTypeId, lifecycle);
    }
    
    
    
    
    //method 추가
    static UA_StatusCode
    helloWorldMethodCallback(UA_Server *server,
    		const UA_NodeId *sessionId, void *sessionHandle,
    		const UA_NodeId *methodId, void *methodContext,
    		const UA_NodeId *objectId, void *objectContext,
    		size_t inputSize, const UA_Variant *input,
    		size_t outputSize, UA_Variant *output) {
    	UA_String *inputStr = (UA_String*)input->data;
    	UA_String tmp = UA_STRING_ALLOC("Hello ");
    	if(inputStr->length > 0) {
    		tmp.data = (UA_Byte *)UA_realloc(tmp.data, tmp.length + inputStr->length);
    		memcpy(&tmp.data[tmp.length], inputStr->data, inputStr->length);
    		tmp.length += inputStr->length;
    	}
    	UA_Variant_setScalarCopy(output, &tmp, &UA_TYPES[UA_TYPES_STRING]);
    	UA_String_clear(&tmp);
    	UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "Hello World was called");
    	return UA_STATUSCODE_GOOD;
    }
    
    static void
    addHellWorldMethod(UA_Server *server) {
    	UA_Argument inputArgument;
    	UA_Argument_init(&inputArgument);
    	inputArgument.description = UA_LOCALIZEDTEXT("en-US", "A String");
    	inputArgument.name = UA_STRING("MyInput");
    	inputArgument.dataType = UA_TYPES[UA_TYPES_STRING].typeId;
    	inputArgument.valueRank = UA_VALUERANK_SCALAR;
    
    	UA_Argument outputArgument;
    	UA_Argument_init(&outputArgument);
    	outputArgument.description = UA_LOCALIZEDTEXT("en-US", "A String");
    	outputArgument.name = UA_STRING("MyOutput");
    	outputArgument.dataType = UA_TYPES[UA_TYPES_STRING].typeId;
    	outputArgument.valueRank = UA_VALUERANK_SCALAR;
    
    	UA_MethodAttributes helloAttr = UA_MethodAttributes_default;
    	helloAttr.description = UA_LOCALIZEDTEXT("en-US","Say `Hello World`");
    	helloAttr.displayName = UA_LOCALIZEDTEXT("en-US","Hello World");
    	helloAttr.executable = true;
    	helloAttr.userExecutable = true;
    	UA_Server_addMethodNode(server, UA_NODEID_NUMERIC(1,62541),
    			UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER),
    			UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT),
    			UA_QUALIFIEDNAME(1, "hello world"),
    			helloAttr, &helloWorldMethodCallback,
    			1, &inputArgument, 1, &outputArgument, NULL, NULL);
    }
    
    
    //이름으로 nodeId를 찾는 함수.
    UA_StatusCode
    findNodeIdWithName(UA_Server *server, UA_NodeId *startNodeId, char *name, UA_NodeId **retNodeId){
    
    	/* Find the NodeId of the status child variable */
    	UA_RelativePathElement rpe;
    	UA_RelativePathElement_init(&rpe);
    	rpe.referenceTypeId = UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT);
    	rpe.isInverse = false;
    	rpe.includeSubtypes = false;
    	rpe.targetName = UA_QUALIFIEDNAME(1,name); 
    
    	UA_BrowsePath bp;
    	UA_BrowsePath_init(&bp);
    
    
    	bp.startingNode = *startNodeId;
    	bp.relativePath.elementsSize = 1;
    	bp.relativePath.elements = &rpe;
    
    	UA_BrowsePathResult bpr =
    		UA_Server_translateBrowsePathToNodeIds(server, &bp);
    	if(bpr.statusCode != UA_STATUSCODE_GOOD ||
    			bpr.targetsSize < 1)
    		return bpr.statusCode;
    	UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "found target is %d", bpr.targets->targetId.nodeId.identifier.numeric);
    	**retNodeId = bpr.targets->targetId.nodeId;
    
    }
    
    
    
    int main(void) {
    	signal(SIGINT, stopHandler);
    	signal(SIGTERM, stopHandler);
    
    	UA_Server *server = UA_Server_new();
    	UA_ServerConfig_setDefault(UA_Server_getConfig(server));
    	UA_ServerConfig* config = UA_Server_getConfig(server);
    	config->verifyRequestTimestamp = UA_RULEHANDLING_ACCEPT;
    
    	//addVariable(server);
    	//writeVariable(server);
    	//writeWrongVariable(server);
    
    
    	//vendor, serial, variable 순으로 추가.
    
    	//공통으로 사용할 부분 설정.
    	UA_NodeId parentNodeId = UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER);
    	UA_NodeId parentReferenceNodeId = UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT);
    	//variable 추가.
    	UA_VariableAttributes varAttr = UA_VariableAttributes_default;
    	UA_Int32 varName = 10;
    	UA_Variant_setScalar(&varAttr.value, &varName, &UA_TYPES[UA_TYPES_INT32]);
    	UA_NodeId varNodeId = UA_NODEID_STRING(1, "Test Var:Me");
    	UA_QualifiedName myVarName = UA_QUALIFIEDNAME(1, "variable");
    	UA_Server_addVariableNode(server, varNodeId, parentNodeId,
    			parentReferenceNodeId, myVarName,
    			UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE), varAttr, NULL, NULL);
    
    	//add callback
    	UA_ValueCallback callback ;
    	callback.onRead = beforeReadVal;
    	callback.onWrite = NULL;
    	UA_NodeId currentNodeId = UA_NODEID_STRING(1, "Test Var:Me");
    	UA_Server_setVariableNode_valueCallback(server, currentNodeId, callback);
    
    	//설비 정의
    
    	defineCylTypes(server);
    	addCylObjectInstance(server, "pin");
    	addCylObjectInstance(server, "latch");
    	addCylObjectInstance(server, "clamp");
    	defineRobotTypes(server);
    	addRbtObjectInstance(server, "Loading Robot");
    
    	/*
    	   defineObjectTypes(server);
    	   addPumpTypeConstructor(server);
    	   addPumpObjectInstance(server, "pump4");
    	 */
    
    
    	//method
    	addHellWorldMethod(server);
    
    
    	//ladder를 시뮬래이션하는 부분.
    	//thread로 latch fwd -> clamp fwd -> pin fwd -> robot in -> pin bwd: 공정 작업시간
    	//         robot in off -> clamp bwd -> latch bwd 순으로 실행
    
    #undef TESe
    #ifdef TEST
    
    	//테스트
    	//각 설비값을 설정하는 부분
    	UA_Variant tmp_value;
    	bool bitoff = 0;
    	UA_Variant_setScalar(&tmp_value, &bitoff, &UA_TYPES[UA_TYPES_BOOLEAN]);
    	UA_NodeId tmpNodeId = UA_NODEID_STRING(1, "Status");
    	UA_Server_writeValue(server, tmpNodeId, tmp_value);
    
    	/* Find the NodeId of the status child variable */
    	UA_RelativePathElement rpe;
    	UA_RelativePathElement_init(&rpe);
    	rpe.referenceTypeId = UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT);
    	rpe.isInverse = false;
    	rpe.includeSubtypes = true;
    	//rpe.targetName = UA_QUALIFIEDNAME(1, "Status");
    	rpe.targetName = UA_QUALIFIEDNAME(1, "latch");
    	//rpe.targetName = UA_QUALIFIEDNAME(1, "variable");
    
    
    	UA_BrowsePath bp;
    	UA_BrowsePath_init(&bp);
    	UA_NodeId *nodeId  = UA_NodeId_new();
    	*nodeId = UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER);
    	//UA_NodeId tmp = {0, UA_NODEIDTYPE_NUMERIC, {50238}};
    	//printf("\nNode id is %d\n", tmp);
    	//UA_NodeId *nodeId = &tmp;
    	bp.startingNode = *nodeId;
    	bp.relativePath.elementsSize = 1;
    	bp.relativePath.elements = &rpe;
    
    	UA_BrowsePathResult bpr =
    		UA_Server_translateBrowsePathToNodeIds(server, &bp);
    	if(bpr.statusCode != UA_STATUSCODE_GOOD ||
    			bpr.targetsSize < 1)
    		return bpr.statusCode;
    
    	UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "found target is %d", bpr.targets->targetId.nodeId.identifier.numeric);
    	//print로 structure를 출력할 수 없음.
    	//printf("\nnode id is %d\n", (UA_NodeId)bpr.targets->targetId.nodeId);
    	//* Set the status value */
    	UA_Boolean status = true;
    	UA_Variant value;
    	UA_Variant_setScalar(&value, &status, &UA_TYPES[UA_TYPES_BOOLEAN]);
    	UA_Server_writeValue(server, bpr.targets->targetId.nodeId, value);
    	//UA_Server_writeValue(server, tmp, value);
    
    	//다시 아래로 내림.
    	bp.startingNode = bpr.targets->targetId.nodeId;
    	rpe.targetName = UA_QUALIFIEDNAME(1, "Status");
    	UA_BrowsePathResult bpr2 =
    		UA_Server_translateBrowsePathToNodeIds(server, &bp);
    	if(bpr2.statusCode != UA_STATUSCODE_GOOD ||
    			bpr2.targetsSize < 1)
    		return bpr.statusCode;
    
    	UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "found target is %d", bpr2.targets->targetId.nodeId.identifier.numeric);
    	UA_Server_writeValue(server, bpr2.targets->targetId.nodeId, value);
    	UA_BrowsePathResult_clear(&bpr);
    #endif
    
    	//NodeId를 저장할 structure 생성
    	struct eqiupNodeId{
    		UA_NodeId latchNodeId;
    		UA_NodeId clampNodeId;
    		UA_NodeId pinNodeId;
    		UA_NodeId robotNodeId;
    	};
    
    	struct eqiupNodeId myEquipNodeId;
    
    	UA_NodeId **nodeIdtmp;
    	UA_NodeId *nodeIdtmp2 = UA_NodeId_new();
    	nodeIdtmp = &nodeIdtmp2;
    	UA_NodeId *nodeId  = UA_NodeId_new();
    	*nodeId = UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER);
    
    	if(	findNodeIdWithName(server, nodeId, "latch", nodeIdtmp) == UA_STATUSCODE_GOOD){
    		nodeId = *nodeIdtmp;
    		if( findNodeIdWithName(server, nodeId, "Status", nodeIdtmp) == UA_STATUSCODE_GOOD){
    			nodeId = *nodeIdtmp;
    			myEquipNodeId.latchNodeId = *nodeId;
    			//printf("Variable 변경\n");
    			//* Set the status value */
    			//			UA_Boolean status = false;
    			//			UA_Variant value;
    			//			UA_Variant_setScalar(&value, &status, &UA_TYPES[UA_TYPES_BOOLEAN]);
    			//			UA_Server_writeValue(server, *nodeId, value);
    			//			UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "node id %d value was written", nodeId->identifier.numeric);
    		}
    	}
    	*nodeId = UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER);
    	if(	findNodeIdWithName(server, nodeId, "clamp", nodeIdtmp) == UA_STATUSCODE_GOOD){
    		nodeId = *nodeIdtmp;
    		if( findNodeIdWithName(server, nodeId, "Status", nodeIdtmp) == UA_STATUSCODE_GOOD){
    			nodeId = *nodeIdtmp;
    			myEquipNodeId.clampNodeId = *nodeId;
    
    			//printf("Variable 변경\n");
    			//* Set the status value */
    			//			UA_Boolean status = false;
    			//			UA_Variant value;
    			//			UA_Variant_setScalar(&value, &status, &UA_TYPES[UA_TYPES_BOOLEAN]);
    			//			UA_Server_writeValue(server, *nodeId, value);
    			//			UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "node id %d value was written", nodeId->identifier.numeric);
    		}
    	}
    
    	*nodeId = UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER);
    	if(	findNodeIdWithName(server, nodeId, "pin", nodeIdtmp) == UA_STATUSCODE_GOOD){
    		nodeId = *nodeIdtmp;
    		if( findNodeIdWithName(server, nodeId, "Status", nodeIdtmp) == UA_STATUSCODE_GOOD){
    			nodeId = *nodeIdtmp;
    			myEquipNodeId.pinNodeId = *nodeId;
    			//printf("Variable 변경\n");
    			//* Set the status value */
    			//			UA_Boolean status = false;
    			//			UA_Variant value;
    			//			UA_Variant_setScalar(&value, &status, &UA_TYPES[UA_TYPES_BOOLEAN]);
    			//			UA_Server_writeValue(server, *nodeId, value);
    			//			UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "node id %d value was written", nodeId->identifier.numeric);
    		}
    	}
    
    	*nodeId = UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER);
    	if(	findNodeIdWithName(server, nodeId, "Loading Robot", nodeIdtmp) == UA_STATUSCODE_GOOD){
    		nodeId = *nodeIdtmp;
    		if( findNodeIdWithName(server, nodeId, "Status", nodeIdtmp) == UA_STATUSCODE_GOOD){
    			nodeId = *nodeIdtmp;
    			myEquipNodeId.robotNodeId = *nodeId;
    			//printf("Variable 변경\n");
    			//* Set the status value */
    			//			UA_Boolean status = false;
    			//			UA_Variant value;
    			//			UA_Variant_setScalar(&value, &status, &UA_TYPES[UA_TYPES_BOOLEAN]);
    			//			UA_Server_writeValue(server, *nodeId, value);
    			//			UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "node id %d value was written", nodeId->identifier.numeric);
    		}
    	}
    
    
    	//구조체 출력.
    	//printf("현재 노드는 %d \n", myEquipNodeId.latchNodeId.identifier.numeric);
    	//printf("현재 노드는 %d \n", myEquipNodeId.clampNodeId.identifier.numeric);
    	//printf("현재 노드는 %d \n", myEquipNodeId.pinNodeId.identifier.numeric);
    	//printf("현재 노드는 %d \n", myEquipNodeId.robotNodeId.identifier.numeric);
    
    	//한번에 업데이트
    	UA_Boolean statusTrue = true;
    	UA_Boolean statusFalse = false;
    	UA_Variant value;
    	UA_Variant_setScalar(&value, &statusFalse, &UA_TYPES[UA_TYPES_BOOLEAN]);
    
    	//초기상태
    	UA_Server_writeValue(server, myEquipNodeId.latchNodeId, value);
    	UA_Server_writeValue(server, myEquipNodeId.clampNodeId, value);
    	UA_Server_writeValue(server, myEquipNodeId.pinNodeId, value);
    	UA_Server_writeValue(server, myEquipNodeId.robotNodeId, value);
    	UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "모든 노드가 초기화(모든 Value가 0) 됨.");
    
    
    
    	//서버 구동.
    	//주기적으로 업데이트로 수정
    	//https://youtu.be/abDnBv5u6bU//
    	UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "Starting server...every 5 secs");
    	UA_StatusCode retval = UA_Server_run_startup(server);
    
    	if(retval != UA_STATUSCODE_GOOD){
    		UA_Server_delete(server);
    		return retval;
    	}
    
    
    	int timestamp = time(0)+5;
    	int equipStates = 0;
    
    	while(running == true){
    		// Handle Server
    		UA_Server_run_iterate(server, true);
    
    		//Update Status Variable
    		if(time(0) > timestamp)
    		{
    			timestamp = time(0) + 5;
    			switch(equipStates)
    			{
    				case 0: //초기상태
    					equipStates = 1;
    
    					UA_Variant_setScalar(&value, &statusFalse, &UA_TYPES[UA_TYPES_BOOLEAN]);
    
    					UA_Server_writeValue(server, myEquipNodeId.latchNodeId, value);
    					UA_Server_writeValue(server, myEquipNodeId.clampNodeId, value);
    					UA_Server_writeValue(server, myEquipNodeId.pinNodeId, value);
    					UA_Server_writeValue(server, myEquipNodeId.robotNodeId, value);
    					UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "모든 노드가 초기화(모든 Value가 0) 됨.");
    					break;
    
    				case 1: //1단계, latch fwd
    					equipStates = 2;
    					UA_Variant_setScalar(&value, &statusFalse, &UA_TYPES[UA_TYPES_BOOLEAN]);
    
    					UA_Server_writeValue(server, myEquipNodeId.clampNodeId, value);
    					UA_Server_writeValue(server, myEquipNodeId.pinNodeId, value);
    					UA_Server_writeValue(server, myEquipNodeId.robotNodeId, value);
    
    					UA_Variant_setScalar(&value, &statusTrue, &UA_TYPES[UA_TYPES_BOOLEAN]);
    					UA_Server_writeValue(server, myEquipNodeId.latchNodeId, value);
    					UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "1단계 동작 완료..latch 전진");
    					break;
    
    				case 2: //2단계, clamp fwd
    					equipStates = 3;
    					UA_Variant_setScalar(&value, &statusFalse, &UA_TYPES[UA_TYPES_BOOLEAN]);
    					UA_Server_writeValue(server, myEquipNodeId.pinNodeId, value);
    					UA_Server_writeValue(server, myEquipNodeId.robotNodeId, value);
    
    					UA_Variant_setScalar(&value, &statusTrue, &UA_TYPES[UA_TYPES_BOOLEAN]);
    					UA_Server_writeValue(server, myEquipNodeId.latchNodeId, value);
    					UA_Server_writeValue(server, myEquipNodeId.clampNodeId, value);
    					UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "2단계 동작 완료..calmp 전진(latch, clamp 전진상태)");
    					break;
    
    				case 3: //3단계, pin fwd
    					equipStates = 4;
    					UA_Variant_setScalar(&value, &statusFalse, &UA_TYPES[UA_TYPES_BOOLEAN]);
    					UA_Server_writeValue(server, myEquipNodeId.robotNodeId, value);
    
    					UA_Variant_setScalar(&value, &statusTrue, &UA_TYPES[UA_TYPES_BOOLEAN]);
    					UA_Server_writeValue(server, myEquipNodeId.latchNodeId, value);
    					UA_Server_writeValue(server, myEquipNodeId.clampNodeId, value);
    					UA_Server_writeValue(server, myEquipNodeId.pinNodeId, value);
    					UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "3단계 동작 완료..pin 전진(latch, clamp, pin 전진상태)");
    					break;
    
    				case 4: //4단계, robot in
    					equipStates = 5;
    					UA_Variant_setScalar(&value, &statusTrue, &UA_TYPES[UA_TYPES_BOOLEAN]);
    
    					UA_Server_writeValue(server, myEquipNodeId.robotNodeId, value);
    					UA_Server_writeValue(server, myEquipNodeId.latchNodeId, value);
    					UA_Server_writeValue(server, myEquipNodeId.clampNodeId, value);
    					UA_Server_writeValue(server, myEquipNodeId.pinNodeId, value);
    					UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "4단계 동작 완료..robot in (latch, clamp, pin 전진상태, robot 진입)");
    					break;
    
    				case 5: //5단계, robot in
    					//다시 초기상태로 이동
    					equipStates = 0;
    					UA_Variant_setScalar(&value, &statusTrue, &UA_TYPES[UA_TYPES_BOOLEAN]);
    
    					UA_Server_writeValue(server, myEquipNodeId.robotNodeId, value);
    					UA_Server_writeValue(server, myEquipNodeId.latchNodeId, value);
    					UA_Server_writeValue(server, myEquipNodeId.clampNodeId, value);
    
    					UA_Variant_setScalar(&value, &statusFalse, &UA_TYPES[UA_TYPES_BOOLEAN]);
    					UA_Server_writeValue(server, myEquipNodeId.pinNodeId, value);
    
    					UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "5단계 동작 완료..pin bwd(latch, clamp, 전진상태, robot 진입, pin 후진)");
    					break;
    
    				default:
    					equipStates = 0;
    					break;
    
    			} //switch
    
    
    		}	//if
    
    
    	} //while
    
    	retval = UA_Server_run_shutdown(server);
    	//UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "Starting server...");
    	//UA_StatusCode retval = UA_Server_run(server, &running);
    	//UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "Shutdown server...");
    
    	UA_Server_delete(server);
    	return retval == UA_STATUSCODE_GOOD ? EXIT_SUCCESS : EXIT_FAILURE;
    } // main

    이렇게 하는게 맞는지 모르겠다. 나중에 client에서 접속하면 안되는건 아니겠지. 다음 단계는 client에서 node를 아니 받아서 mysql에 업로드하면 된다. 이제 라즈베리 파이로 입력 8, 출력 4점 PLC + OPC UA 서버를 만들 수 있다. 신뢰성은 0이어서 의미 없겠지만.

  • open62541 browse

    open62541이 OPC UA 규격에 맞춰 browse 기능을 지원한다. OPC UA 문서에 어떻게 사용하는지 알 수 있는데, 멤버만 볼 수 있다. 일단 대충 필요한 실린더를 구성했다 하자. 상용 PLC 메이커가 OPC UA 기능을 구현한다면 내부에 server 기능으로 넣을 듯 하다. 지금 구할 수 없으니, 대충 비슷하게 만들고 싶다. 여기서 문제를 알았다. 한번 Variable을 할당하면 어떻게 바꾸지? callback으로 바꿀 수 있는데, nodeId를 알아야 한다. nodeId를 null로 설정하면 server가 임의로 할당한다.

    아래 그림 NodeID i=50235, Status Value를 True로 만들고 싶다. 어떻게??

    이럴때 browse를 사용한다.(아마도..) sample로 구현되어 있는데, example 디렉토리 tutorial_server_object.c에 있다. 가장 간단한 방법이 아래와 같다.

    #define TEST
    #ifdef TEST
    
    	//테스트
    	//각 설비값을 설정하는 부분
    	UA_Variant tmp_value;
    	bool bitoff = 0;
    	UA_Variant_setScalar(&tmp_value, &bitoff, &UA_TYPES[UA_TYPES_BOOLEAN]);
    	UA_NodeId tmpNodeId = UA_NODEID_STRING(1, "Status");
    	UA_Server_writeValue(server, tmpNodeId, tmp_value);
    
        /* Find the NodeId of the status child variable */
        UA_RelativePathElement rpe;
        UA_RelativePathElement_init(&rpe);
        rpe.referenceTypeId = UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT);
        rpe.isInverse = false;
        rpe.includeSubtypes = true;
        //rpe.targetName = UA_QUALIFIEDNAME(1, "Status");
    	rpe.targetName = UA_QUALIFIEDNAME(1, "latch");
    	//rpe.targetName = UA_QUALIFIEDNAME(1, "variable");
    
    
        UA_BrowsePath bp;
        UA_BrowsePath_init(&bp);
    	UA_NodeId *nodeId  = UA_NodeId_new();
    	*nodeId = UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER);
    	//UA_NodeId tmp = {0, UA_NODEIDTYPE_NUMERIC, {50238}};
    	//printf("\nNode id is %d\n", tmp);
    	//UA_NodeId *nodeId = &tmp;
        bp.startingNode = *nodeId;
        bp.relativePath.elementsSize = 1;
        bp.relativePath.elements = &rpe;
    
        UA_BrowsePathResult bpr =
            UA_Server_translateBrowsePathToNodeIds(server, &bp);
        if(bpr.statusCode != UA_STATUSCODE_GOOD ||
           bpr.targetsSize < 1)
            return bpr.statusCode;
    
    	UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "found target is %d", bpr.targets->targetId.nodeId.identifier.numeric);
    	//print로 structure를 출력할 수 없음.
    	//printf("\nnode id is %d\n", (UA_NodeId)bpr.targets->targetId.nodeId);
        //* Set the status value */
        UA_Boolean status = true;
        UA_Variant value;
        UA_Variant_setScalar(&value, &status, &UA_TYPES[UA_TYPES_BOOLEAN]);
        UA_Server_writeValue(server, bpr.targets->targetId.nodeId, value);
    	//UA_Server_writeValue(server, tmp, value);
        UA_BrowsePathResult_clear(&bpr);
    #endif

    여기를 gdb로 보면 시작 위치를 설정, 알고 있는 정보를 입력, 두 구조체를 비교하여 찾는다. 찾으면 0을 반환한다.

    450	    UA_RelativePathElement_init(&rpe);
    (gdb) 
    451	    rpe.referenceTypeId = UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT);
    (gdb) 
    452	    rpe.isInverse = false;
    (gdb) 
    453	    rpe.includeSubtypes = true;
    (gdb) 
    455		rpe.targetName = UA_QUALIFIEDNAME(1, "latch");
    (gdb) 
    460	    UA_BrowsePath_init(&bp);
    (gdb) 
    461		UA_NodeId *nodeId  = UA_NodeId_new();
    (gdb) 
    462		*nodeId = UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER);
    (gdb) 
    466	    bp.startingNode = *nodeId;
    (gdb) 
    467	    bp.relativePath.elementsSize = 1;
    (gdb) 
    468	    bp.relativePath.elements = &rpe;
    (gdb) 
    471	        UA_Server_translateBrowsePathToNodeIds(server, &bp);
    (gdb) 
    472	    if(bpr.statusCode != UA_STATUSCODE_GOOD ||
    (gdb) display rpe
    1: rpe = {referenceTypeId = {namespaceIndex = 0, identifierType = UA_NODEIDTYPE_NUMERIC, identifier = {numeric = 47, string = {length = 47, 
            data = 0x136b4 <main+692> "b?K\342\003"}, guid = {data1 = 47, data2 = 14004, data3 = 1, data4 = "\257\362\377\276l\362\377\276"}, byteString = {
            length = 47, data = 0x136b4 <main+692> "b?K\342\003"}}}, isInverse = false, includeSubtypes = true, targetName = {namespaceIndex = 1, name = {
          length = 5, data = 0x13bbc "latch"}}}
    (gdb) display bp
    2: bp = {startingNode = {namespaceIndex = 0, identifierType = UA_NODEIDTYPE_NUMERIC, identifier = {numeric = 85, string = {length = 85, data = 0x52a48 ""}, 
          guid = {data1 = 85, data2 = 10824, data3 = 5, data4 = "\024\361\377\276\234\016\001"}, byteString = {length = 85, data = 0x52a48 ""}}}, 
      relativePath = {elementsSize = 1, elements = 0xbefff26c}}
    (gdb) display bpr
    3: bpr = {statusCode = 0, targetsSize = 1, targets = 0x2c9c8}
    (gdb) display *bpr.targets
    4: *bpr.targets = {targetId = {nodeId = {namespaceIndex = 0, identifierType = UA_NODEIDTYPE_NUMERIC, identifier = {numeric = 50234, string = {
              length = 50234, data = 0x0}, guid = {data1 = 50234, data2 = 0, data3 = 0, data4 = "\000\000\000\000\000\000\000"}, byteString = {length = 50234, 
              data = 0x0}}}, namespaceUri = {length = 0, data = 0x0}, serverIndex = 0}, remainingPathIndex = 4294967295}
    (gdb) n
    473	       bpr.targetsSize < 1)
    1: rpe = {referenceTypeId = {namespaceIndex = 0, identifierType = UA_NODEIDTYPE_NUMERIC, identifier = {numeric = 47, string = {length = 47, 
            data = 0x136b4 <main+692> "b?K\342\003"}, guid = {data1 = 47, data2 = 14004, data3 = 1, data4 = "\257\362\377\276l\362\377\276"}, byteString = {
            length = 47, data = 0x136b4 <main+692> "b?K\342\003"}}}, isInverse = false, includeSubtypes = true, targetName = {namespaceIndex = 1, name = {
          length = 5, data = 0x13bbc "latch"}}}
    2: bp = {startingNode = {namespaceIndex = 0, identifierType = UA_NODEIDTYPE_NUMERIC, identifier = {numeric = 85, string = {length = 85, data = 0x52a48 ""}, 
          guid = {data1 = 85, data2 = 10824, data3 = 5, data4 = "\024\361\377\276\234\016\001"}, byteString = {length = 85, data = 0x52a48 ""}}}, 
      relativePath = {elementsSize = 1, elements = 0xbefff26c}}
    3: bpr = {statusCode = 0, targetsSize = 1, targets = 0x2c9c8}
    4: *bpr.targets = {targetId = {nodeId = {namespaceIndex = 0, identifierType = UA_NODEIDTYPE_NUMERIC, identifier = {numeric = 50234, string = {
              length = 50234, data = 0x0}, guid = {data1 = 50234, data2 = 0, data3 = 0, data4 = "\000\000\000\000\000\000\000"}, byteString = {length = 50234, 
              data = 0x0}}}, namespaceUri = {length = 0, data = 0x0}, serverIndex = 0}, remainingPathIndex = 4294967295}
    

    어디에서 시작할 지 정해줘야 한다. 가장 상위인 Object에서 시작했다. NodeId i=84다. 이 번호도 정했을 것 같다. 문제는 이 설정이 어떻게 되어 있는지 Objects 바로 아래 항목만 찾는다. 위 방식으로 NodeId i=50234까지는 찾았다. 그러나 Varible이 없어 write를 하더라도 효과 없다. 하위 항목인 1:Status, NodeId i=50235를 찾아야 했다. 편법으로 시작점을 50243으로 넣어 줬다.

    #define TEST
    #ifdef TEST
    
    	//테스트
    	//각 설비값을 설정하는 부분
    	UA_Variant tmp_value;
    	bool bitoff = 0;
    	UA_Variant_setScalar(&tmp_value, &bitoff, &UA_TYPES[UA_TYPES_BOOLEAN]);
    	UA_NodeId tmpNodeId = UA_NODEID_STRING(1, "Status");
    	UA_Server_writeValue(server, tmpNodeId, tmp_value);
    
        /* Find the NodeId of the status child variable */
        UA_RelativePathElement rpe;
        UA_RelativePathElement_init(&rpe);
        rpe.referenceTypeId = UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT);
        rpe.isInverse = false;
        rpe.includeSubtypes = true;
        //rpe.targetName = UA_QUALIFIEDNAME(1, "Status");
    	rpe.targetName = UA_QUALIFIEDNAME(1, "latch");
    	//rpe.targetName = UA_QUALIFIEDNAME(1, "variable");
    
    
        UA_BrowsePath bp;
        UA_BrowsePath_init(&bp);
    	UA_NodeId *nodeId  = UA_NodeId_new();
    	*nodeId = UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER);
    	//UA_NodeId tmp = {0, UA_NODEIDTYPE_NUMERIC, {50238}};
    	//printf("\nNode id is %d\n", tmp);
    	//UA_NodeId *nodeId = &tmp;
        bp.startingNode = *nodeId;
        bp.relativePath.elementsSize = 1;
        bp.relativePath.elements = &rpe;
    
        UA_BrowsePathResult bpr =
            UA_Server_translateBrowsePathToNodeIds(server, &bp);
        if(bpr.statusCode != UA_STATUSCODE_GOOD ||
           bpr.targetsSize < 1)
            return bpr.statusCode;
    
    	UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "found target is %d", bpr.targets->targetId.nodeId.identifier.numeric);
    	//print로 structure를 출력할 수 없음.
    	//printf("\nnode id is %d\n", (UA_NodeId)bpr.targets->targetId.nodeId);
        //* Set the status value */
        UA_Boolean status = true;
        UA_Variant value;
        UA_Variant_setScalar(&value, &status, &UA_TYPES[UA_TYPES_BOOLEAN]);
        UA_Server_writeValue(server, bpr.targets->targetId.nodeId, value);
    	//UA_Server_writeValue(server, tmp, value);
    
    	//다시 아래로 내림.
    	bp.startingNode = bpr.targets->targetId.nodeId;
    	rpe.targetName = UA_QUALIFIEDNAME(1, "Status");
        UA_BrowsePathResult bpr2 =
            UA_Server_translateBrowsePathToNodeIds(server, &bp);
        if(bpr2.statusCode != UA_STATUSCODE_GOOD ||
           bpr2.targetsSize < 1)
            return bpr.statusCode;
    
    	UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "found target is %d", bpr2.targets->targetId.nodeId.identifier.numeric);
    	UA_Server_writeValue(server, bpr2.targets->targetId.nodeId, value);
        UA_BrowsePathResult_clear(&bpr);
    #endif
    

    Variable을 제대로 수정한다. 이제 rucursive browse를 어떻게 해야 하는지 알아야겠다. 삽질해도 보람차다.ㅠㅠ

  • open62541 server 설정

    tutorial을 따라하다 보면 내가 무엇을 모르는지 잘 모른다. 아래 같은 그림을 그리고 싶었는데 잘 안된다. 내가 서버를 건드릴 일은 없겠지만, 클라이언트를 테스트하려면 서버가 필요하다.

    #include "open62541.h"
    
    #include <signal.h>
    #include <stdlib.h>
    
    static volatile UA_Boolean running = true;
    static void stopHandler(int sig) {
        UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "received ctrl-c");
        running = false;
    }
    
    static void
    beforeReadVal(UA_Server *server,
                   const UA_NodeId *sessionId, void *sessionContext,
                   const UA_NodeId *nodeid, void *nodeContext,
                   const UA_NumericRange *range, const UA_DataValue *data) {
        //updateCurrentTime(server);
    	int tmpVal = rand()%1000;
        UA_Variant value;
        UA_Variant_setScalar(&value, &tmpVal, &UA_TYPES[UA_TYPES_INT32]);
        UA_NodeId currentNodeId = UA_NODEID_STRING(1, "Test Var:Me");
        UA_Server_writeValue(server, currentNodeId, value);
    }
    
    
    /* predefined identifier for later use */
    UA_NodeId pumpTypeId = {1, UA_NODEIDTYPE_NUMERIC, {1001}};
    UA_NodeId cylTypeId = {1, UA_NODEIDTYPE_NUMERIC, {2001}};
    
    static void
    defineObjectTypes(UA_Server *server) {
        /* Define the object type for "Device" */
        UA_NodeId deviceTypeId; /* get the nodeid assigned by the server */
        UA_ObjectTypeAttributes dtAttr = UA_ObjectTypeAttributes_default;
        dtAttr.displayName = UA_LOCALIZEDTEXT("en-US", "DeviceType");
        UA_Server_addObjectTypeNode(server, UA_NODEID_NULL,
                                    UA_NODEID_NUMERIC(0, UA_NS0ID_BASEOBJECTTYPE),
                                    UA_NODEID_NUMERIC(0, UA_NS0ID_HASSUBTYPE),
                                    UA_QUALIFIEDNAME(1, "DeviceType"), dtAttr,
                                    NULL, &deviceTypeId);
    
        UA_VariableAttributes mnAttr = UA_VariableAttributes_default;
        mnAttr.displayName = UA_LOCALIZEDTEXT("en-US", "ManufacturerName");
        UA_NodeId manufacturerNameId;
        UA_Server_addVariableNode(server, UA_NODEID_NULL, deviceTypeId,
                                  UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT),
                                  UA_QUALIFIEDNAME(1, "ManufacturerName"),
                                  UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE), mnAttr, NULL, &manufacturerNameId);
        /* Make the manufacturer name mandatory */
        UA_Server_addReference(server, manufacturerNameId,
                               UA_NODEID_NUMERIC(0, UA_NS0ID_HASMODELLINGRULE),
                               UA_EXPANDEDNODEID_NUMERIC(0, UA_NS0ID_MODELLINGRULE_MANDATORY), true);
    
    
        UA_VariableAttributes modelAttr = UA_VariableAttributes_default;
        modelAttr.displayName = UA_LOCALIZEDTEXT("en-US", "ModelName");
        UA_Server_addVariableNode(server, UA_NODEID_NULL, deviceTypeId,
                                  UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT),
                                  UA_QUALIFIEDNAME(1, "ModelName"),
                                  UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE), modelAttr, NULL, NULL);
    
        /* Define the object type for "Pump" */
        UA_ObjectTypeAttributes ptAttr = UA_ObjectTypeAttributes_default;
        ptAttr.displayName = UA_LOCALIZEDTEXT("en-US", "PumpType");
        UA_Server_addObjectTypeNode(server, pumpTypeId,
                                    deviceTypeId, UA_NODEID_NUMERIC(0, UA_NS0ID_HASSUBTYPE),
                                    UA_QUALIFIEDNAME(1, "PumpType"), ptAttr,
                                    NULL, NULL);
    
        UA_VariableAttributes statusAttr = UA_VariableAttributes_default;
        statusAttr.displayName = UA_LOCALIZEDTEXT("en-US", "Status");
        statusAttr.valueRank = UA_VALUERANK_SCALAR;
        UA_NodeId statusId;
        UA_Server_addVariableNode(server, UA_NODEID_NULL, pumpTypeId,
                                  UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT),
                                  UA_QUALIFIEDNAME(1, "Status"),
                                  UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE), statusAttr, NULL, &statusId);
        /* Make the status variable mandatory */
        UA_Server_addReference(server, statusId,
                               UA_NODEID_NUMERIC(0, UA_NS0ID_HASMODELLINGRULE),
                               UA_EXPANDEDNODEID_NUMERIC(0, UA_NS0ID_MODELLINGRULE_MANDATORY), true);
    
        UA_VariableAttributes rpmAttr = UA_VariableAttributes_default;
        rpmAttr.displayName = UA_LOCALIZEDTEXT("en-US", "MotorRPM");
        rpmAttr.valueRank = UA_VALUERANK_SCALAR;
        UA_Server_addVariableNode(server, UA_NODEID_NULL, pumpTypeId,
                                  UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT),
                                  UA_QUALIFIEDNAME(1, "MotorRPMs"),
                                  UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE), rpmAttr, NULL, NULL);
    }
    
    static void
    defineCylTypes(UA_Server *server) {
        /* Define the object type for "Device" */
        UA_NodeId deviceTypeId; /* get the nodeid assigned by the server */
        UA_ObjectTypeAttributes dtAttr = UA_ObjectTypeAttributes_default;
        dtAttr.displayName = UA_LOCALIZEDTEXT("en-US", "Cylinder Type");
        UA_Server_addObjectTypeNode(server, UA_NODEID_NULL,
                                    UA_NODEID_NUMERIC(0, UA_NS0ID_BASEOBJECTTYPE),
                                    UA_NODEID_NUMERIC(0, UA_NS0ID_HASSUBTYPE),
                                    UA_QUALIFIEDNAME(1, "Cylinder Type"), dtAttr,
                                    NULL, &deviceTypeId);
    
        UA_VariableAttributes mnAttr = UA_VariableAttributes_default;
        mnAttr.displayName = UA_LOCALIZEDTEXT("en-US", "Manufacturer Name");
        UA_NodeId manufacturerNameId;
        UA_Server_addVariableNode(server, UA_NODEID_NULL, deviceTypeId,
                                  UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT),
                                  UA_QUALIFIEDNAME(1, "Cylinder"),
                                  UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE), mnAttr, NULL, &manufacturerNameId);
    
        UA_VariableAttributes modelAttr = UA_VariableAttributes_default;
    	UA_NodeId modelNameId;
        modelAttr.displayName = UA_LOCALIZEDTEXT("en-US", "Cyl Model Name");
        UA_Server_addVariableNode(server, UA_NODEID_NULL, deviceTypeId,
                                  UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT),
                                  UA_QUALIFIEDNAME(1, "Cyl Model Name"),
                                  UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE), modelAttr, NULL, &modelNameId);
    
        UA_Server_addReference(server,modelNameId,
                               UA_NODEID_NUMERIC(0, UA_NS0ID_HASMODELLINGRULE),
                               UA_EXPANDEDNODEID_NUMERIC(0, UA_NS0ID_MODELLINGRULE_MANDATORY), true);
    
        /* Define the object type for "Pump" */
        UA_ObjectTypeAttributes ptAttr = UA_ObjectTypeAttributes_default;
        ptAttr.displayName = UA_LOCALIZEDTEXT("en-US", "Running State");
        UA_Server_addObjectTypeNode(server, cylTypeId,
                                    deviceTypeId, UA_NODEID_NUMERIC(0, UA_NS0ID_HASSUBTYPE),
                                    UA_QUALIFIEDNAME(1, "Running State"), ptAttr,
                                    NULL, NULL);
    
    
        UA_VariableAttributes statusAttr = UA_VariableAttributes_default;
        statusAttr.displayName = UA_LOCALIZEDTEXT("en-US", "Status");
        statusAttr.valueRank = UA_VALUERANK_SCALAR;
        UA_NodeId statusId;
        UA_Server_addVariableNode(server, UA_NODEID_NULL, cylTypeId,
                                  UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT),
                                  UA_QUALIFIEDNAME(1, "Status"),
                                  UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE), statusAttr, NULL, &statusId);
        /* Make the manufacturer name mandatory */
    	// UA_Server_addReference()를 어떻게 하냐에 따라  statusId
    	// instance 만들경우 표시
        UA_Server_addReference(server, statusId,
                               UA_NODEID_NUMERIC(0, UA_NS0ID_HASMODELLINGRULE),
                               UA_EXPANDEDNODEID_NUMERIC(0, UA_NS0ID_MODELLINGRULE_MANDATORY), true);
    }
    
    static void
    addPumpObjectInstance(UA_Server *server, char *name) {
        UA_ObjectAttributes oAttr = UA_ObjectAttributes_default;
        oAttr.displayName = UA_LOCALIZEDTEXT("en-US", name);
        UA_Server_addObjectNode(server, UA_NODEID_NULL,
                                UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER),
                                UA_NODEID_NUMERIC(0, UA_NS0ID_ORGANIZES),
                                UA_QUALIFIEDNAME(1, name),
                                pumpTypeId, /* this refers to the object type
                                               identifier */
                                oAttr, NULL, NULL);
    }
    
    static void
    addCylObjectInstance(UA_Server *server, char *name) {
        UA_ObjectAttributes oAttr = UA_ObjectAttributes_default;
        oAttr.displayName = UA_LOCALIZEDTEXT("en-US", name);
    	/*
    	UA_Server_addObjectNode(UA_Server *server, const UA_NodeId requestedNewNodeId,
                            const UA_NodeId parentNodeId,
                            const UA_NodeId referenceTypeId,
                            const UA_QualifiedName browseName,
                            const UA_NodeId typeDefinition,
                            const UA_ObjectAttributes attr,
                            void *nodeContext, UA_NodeId *outNewNodeId) {
    						*/
    
        UA_Server_addObjectNode(server, UA_NODEID_NULL,
                                UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER),
                                UA_NODEID_NUMERIC(0, UA_NS0ID_ORGANIZES),
                                UA_QUALIFIEDNAME(1, name),
                                cylTypeId, /* this refers to the object type
                                               identifier */
                                oAttr, NULL, NULL);
    }
    
    static UA_StatusCode
    pumpTypeConstructor(UA_Server *server,
                        const UA_NodeId *sessionId, void *sessionContext,
                        const UA_NodeId *typeId, void *typeContext,
                        const UA_NodeId *nodeId, void **nodeContext) {
        UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "New pump created");
    
        /* Find the NodeId of the status child variable */
        UA_RelativePathElement rpe;
        UA_RelativePathElement_init(&rpe);
        rpe.referenceTypeId = UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT);
        rpe.isInverse = false;
        rpe.includeSubtypes = false;
        rpe.targetName = UA_QUALIFIEDNAME(1, "Status");
    
        UA_BrowsePath bp;
        UA_BrowsePath_init(&bp);
        bp.startingNode = *nodeId;
        bp.relativePath.elementsSize = 1;
        bp.relativePath.elements = &rpe;
    
        UA_BrowsePathResult bpr =
            UA_Server_translateBrowsePathToNodeIds(server, &bp);
        if(bpr.statusCode != UA_STATUSCODE_GOOD ||
           bpr.targetsSize < 1)
            return bpr.statusCode;
    
        /* Set the status value */
        UA_Boolean status = true;
        UA_Variant value;
        UA_Variant_setScalar(&value, &status, &UA_TYPES[UA_TYPES_BOOLEAN]);
        UA_Server_writeValue(server, bpr.targets[0].targetId.nodeId, value);
        UA_BrowsePathResult_clear(&bpr);
    
        /* At this point we could replace the node context .. */
    
        return UA_STATUSCODE_GOOD;
    }
    
    static void
    addPumpTypeConstructor(UA_Server *server) {
        UA_NodeTypeLifecycle lifecycle;
        lifecycle.constructor = pumpTypeConstructor;
        lifecycle.destructor = NULL;
        UA_Server_setNodeTypeLifecycle(server, pumpTypeId, lifecycle);
    }
    
    
    
    
    //method 추가
    static UA_StatusCode
    helloWorldMethodCallback(UA_Server *server,
                             const UA_NodeId *sessionId, void *sessionHandle,
                             const UA_NodeId *methodId, void *methodContext,
                             const UA_NodeId *objectId, void *objectContext,
                             size_t inputSize, const UA_Variant *input,
                             size_t outputSize, UA_Variant *output) {
        UA_String *inputStr = (UA_String*)input->data;
        UA_String tmp = UA_STRING_ALLOC("Hello ");
        if(inputStr->length > 0) {
            tmp.data = (UA_Byte *)UA_realloc(tmp.data, tmp.length + inputStr->length);
            memcpy(&tmp.data[tmp.length], inputStr->data, inputStr->length);
            tmp.length += inputStr->length;
        }
        UA_Variant_setScalarCopy(output, &tmp, &UA_TYPES[UA_TYPES_STRING]);
        UA_String_clear(&tmp);
        UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "Hello World was called");
        return UA_STATUSCODE_GOOD;
    }
    
    static void
    addHellWorldMethod(UA_Server *server) {
        UA_Argument inputArgument;
        UA_Argument_init(&inputArgument);
        inputArgument.description = UA_LOCALIZEDTEXT("en-US", "A String");
        inputArgument.name = UA_STRING("MyInput");
        inputArgument.dataType = UA_TYPES[UA_TYPES_STRING].typeId;
        inputArgument.valueRank = UA_VALUERANK_SCALAR;
    
        UA_Argument outputArgument;
        UA_Argument_init(&outputArgument);
        outputArgument.description = UA_LOCALIZEDTEXT("en-US", "A String");
        outputArgument.name = UA_STRING("MyOutput");
        outputArgument.dataType = UA_TYPES[UA_TYPES_STRING].typeId;
        outputArgument.valueRank = UA_VALUERANK_SCALAR;
    
        UA_MethodAttributes helloAttr = UA_MethodAttributes_default;
        helloAttr.description = UA_LOCALIZEDTEXT("en-US","Say `Hello World`");
        helloAttr.displayName = UA_LOCALIZEDTEXT("en-US","Hello World");
        helloAttr.executable = true;
        helloAttr.userExecutable = true;
        UA_Server_addMethodNode(server, UA_NODEID_NUMERIC(1,62541),
                                UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER),
                                UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT),
                                UA_QUALIFIEDNAME(1, "hello world"),
                                helloAttr, &helloWorldMethodCallback,
                                1, &inputArgument, 1, &outputArgument, NULL, NULL);
    }
    
    
    int main(void) {
        signal(SIGINT, stopHandler);
        signal(SIGTERM, stopHandler);
    
        UA_Server *server = UA_Server_new();
        UA_ServerConfig_setDefault(UA_Server_getConfig(server));
    	UA_ServerConfig* config = UA_Server_getConfig(server);
    	config->verifyRequestTimestamp = UA_RULEHANDLING_ACCEPT;
    
    	//addVariable(server);
    	//writeVariable(server);
    	//writeWrongVariable(server);
    
    
    	//vendor, serial, variable 순으로 추가.
    
    	//공통으로 사용할 부분 설정.
    	UA_NodeId parentNodeId = UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER);
    	UA_NodeId parentReferenceNodeId = UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT);
    	//variable 추가.
    	UA_VariableAttributes varAttr = UA_VariableAttributes_default;
    	UA_Int32 varName = 10;
        UA_Variant_setScalar(&varAttr.value, &varName, &UA_TYPES[UA_TYPES_INT32]);
        UA_NodeId varNodeId = UA_NODEID_STRING(1, "Test Var:Me");
    	UA_QualifiedName myVarName = UA_QUALIFIEDNAME(1, "variable");
        UA_Server_addVariableNode(server, varNodeId, parentNodeId,
                                  parentReferenceNodeId, myVarName,
                                  UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE), varAttr, NULL, NULL);
    
    	//add callback
        UA_ValueCallback callback ;
        callback.onRead = beforeReadVal;
        callback.onWrite = NULL;
    	UA_NodeId currentNodeId = UA_NODEID_STRING(1, "Test Var:Me");
        UA_Server_setVariableNode_valueCallback(server, currentNodeId, callback);
    
    	defineObjectTypes(server);
    	defineCylTypes(server);
    	addPumpObjectInstance(server, "pump2");
    	addPumpObjectInstance(server, "pump3");
    	addPumpTypeConstructor(server);
    	addPumpObjectInstance(server, "pump4");
    	addPumpObjectInstance(server, "pump5");
    
    	addCylObjectInstance(server, "pin1");
    	addCylObjectInstance(server, "pin2");
    
    
    
    
    	//method
    	addHellWorldMethod(server);
    
    
    	//서버 구동.
    	UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "Starting server...");
    	UA_StatusCode retval = UA_Server_run(server, &running);
    	UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "Shutdown server...");
    
    
    	UA_Server_delete(server);
    	return retval == UA_STATUSCODE_GOOD ? EXIT_SUCCESS : EXIT_FAILURE;
    }

    object를 define할 때 어느 변수를 인스턴스에 포함할지 결정할 수 있다. defineCylTypes 함수 중 UA_Server_addReference의 변수로 전에 선언한 id를 넘겨주면 붙는다.

        UA_NodeId statusId;
        UA_Server_addVariableNode(server, UA_NODEID_NULL, cylTypeId,
                                  UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT),
                                  UA_QUALIFIEDNAME(1, "Status"),
                                  UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE), statusAttr, NULL, &statusId);
        /* Make the manufacturer name mandatory */
    	// UA_Server_addReference()를 어떻게 하냐에 따라  statusId
    	// instance 만들경우 표시
        UA_Server_addReference(server, statusId,
                               UA_NODEID_NUMERIC(0, UA_NS0ID_HASMODELLINGRULE),
                               UA_EXPANDEDNODEID_NUMERIC(0, UA_NS0ID_MODELLINGRULE_MANDATORY), true);

    꼬리의 꼬리를 무는 OPC UA.