[Platinum UPnP] 101 :: (2) Device에 Service 올리기 + StateVariable & Event




※ 주의: 앞으로 나오는 이상한 문자열이나 XML에 대해서 친절하게 설명하지 않습니다. 눈치것 의미를 알아차리시거나, 스펙문서를 통해 의미를 파악해 주시기 바랍니다. 이 문서의 목적은 Platinum UPnP를 이용해 잘 동작하는 Tutorial 프로그램을 작성하는 것이니까요.



앞에서 우리는 간단한 Device를 만들어서 확인해 보았습니다. 하지만, UPnP에서 Device만으로는 아무런 소용이 없습니다. UPnP는 Device가 소유하고 있는 Service를 통해서만 Device를 조작할 수 있습니다. 이러한 동작을 위해 Service는 다시 Action과 StateVariable, Event라는 개념을 갖고 있습니다. Action은 외부에서의 명령, StateVariable은 말 그대로 상태값(음량, 음소거 상태 등등)이며, Event는 StateVariable의 변경이 일어났을 때, 만약 그것을 외부에 알리도록 설정되어 있다면 변경을 공지합니다.


아마 Windows의 COM/DCOM/ActiveX 개념을 알고 있다면, 위의 내용을 이해하기 훨씬 쉬울 겁니다. 임의의 바이너리를 범용적으로 연결하여 동작하기 위한 기본 원리는 완벽하게 동일하다고 말할 수 있겠네요. 뭐, 모른다면 할 수 없구요 ㅋ


    이번 작업은 앞서 했던 파일(tutorial01.cpp)을 바탕으로 수정하도록 하겠습니다. 

    먼저, 성의가 없었던 Device의 생성자를 살짝 수정합니다. 수정된 코드는 아래와 같습니다.


TutorialDevice()
	: PLT_DeviceHost("/", "", "urn:schemas-upnp-org:device:Tutorial:1", "Tutorial Device")
{
}


    이제 Service를 생성해야 합니다. Service는 XML 형식의 Service Description에 의해 그 형태가 결정 됩니다. Service Description에는 그 Service가 갖는 Action과 StateVariable, Event에 대해 기술되어 있습니다.

    일단 복잡함을 피하기 위하여, StateVariable과 Event에 대한 동작을 먼저 만들어 보겠습니다. 지금 구현할 동작을 명세하자면, 콘솔 입력을 통해 문자열을 입력 받은 후 그것을 StateVariable에 저장하여 Event가 발생하는지를 확인합니다.

    우선 Service Description XML을 준비해야 합니다. 그래서 아래와 같은 변수를 정의합니다.


const char* SCPDXML_TUTORIAL =
	"<?xml version=\"1.0\" ?>"
	"  <scpd xmlns=\"urn:schemas-upnp-org:service-1-0\">"
	"    <specversion>"
	"       <major>1</major>"
	"	    <minor>0</minor>"
	"	 </specversion>"
	"    <servicestatetable>"
	"      <statevariable sendevents=\"yes\">"
	"        <name>PrintBuffer</name>"
	"        <datatype>string</datatype>"
	"        <defaultvalue></defaultvalue>"
	"      </statevariable>"
	"    </servicestatetable>"
	"  </scpd>";


    위의 XML에서는 현재 Service 안에 문자열(string)로 취급되는 "PrintBuffer"라는 이름의 StateVariable이 하나 있으며, 변경시 Event가 발생한다는 것을 알려주고 있습니다. 이제 Device에 이 Service를 등록해야 합니다.

    전 단계에서 PLT_DeviceHost 클래스의 자식 클래스를 만들기 위해 정의했던 함수가 있습니다. 바로 SetupServices() 함수 였습니다. 기억하시는지요? Device에 Service를 등록하기 위해서는 이 함수 안에서 등록해 주어야 합니다. 그래서 아래와 같이 SetupServices() 함수에 내용물을 넣습니다.


virtual NPT_Result SetupServices(void)
{
	PLT_Service* service = new PLT_Service(
		this,
		"urn:schemas-upnp-org:service:Tutorial:1", 
		"urn:upnp-org:serviceId:Tutorial.001",
		"Tutorial");
	service->SetSCPDXML(SCPDXML_TUTORIAL);
	AddService(service);

	service->SetStateVariable("Status", "True");

	return NPT_SUCCESS;
}


    이렇게 함으로써 "urn:upnp-org:serviceId:Tutorial.001"이라는 아이디와 "Tutorial"이라는 이름을 갖는 Service가 Device에 등록 되었습니다.

    StateVariable과 Event가 정상 동작하는지 판별하기 위해서는 수동으로 값을 변경해 주어야 할 필요가 있습니다. 그래서 아래와 같이 외부에서 "PrintBuffer"를 조작하기 위한 함수를 하나 만들겠습니다.


void SetPrintBuffer(const char* str)
{
	PLT_Service* service;
	if (FindServiceByName("Tutorial", service) == NPT_SUCCESS) {
		service->SetStateVariable("PrintBuffer", str);
	}
}


    FindServiceByName() 함수는 PLT_DeviceHost의 부모 클래스인 PLT_DeviceData의 멤버함수 입니다. 위 함수 이외에도 다양한 방법으로 Device가 소유한 Service를 찾을 수 있습니다. Service를 찾고나면 원하는 StateVariable에 입력값을 저장합니다.

    마지막으로 콘솔에서 값을 입력 받아 위에서 정의한 SetPrintBuffer() 함수를 호출하도록 main() 함수의 while loop 구문을 아래와 같이 수정합니다.


bool stop = false;
do {
	std::string input;
	std::cin >> input;
	if (input == "q" || input == "Q") {
		stop = true;
	} else {
		tutorial_device->SetPrintBuffer(input.c_str());
	}
} while(!stop);


    이제 모든 작업이 완료 되었습니다. 코드를 빌드하여 실행시킨 후 StateVariable을 변경시켜 Event가 발생하는지 Device Spy로 확인할 차례입니다. 여기서 주의할 점은 Device Spy의 해당 서비스에서 마우스 오른쪽 버튼으로 컨텍스트 메뉴를 연 뒤 "Subscribe to Events" 항목을 클릭해야 해당 Service의 Event가 Device Spy에 나타나게 됩니다.

    확인해 본 결과 저는 아주 잘 동작하고 있습니다. 여러분은 어떠신가요?