[Platinum UPnP] 101 :: (6) Device에서 Event 받기




파일: tutorial06.cpp


이제 Platinum UPnP 101 Tutorial도 막바지에 왔습니다. 이번에는 끝으로 Control-Point에서 Device의 Event를 받는 기능을 구현하도록 하겠습니다.

    하지만, 기능 구현에 앞서 약간의 리팩토링을 하고 진행하도록 하겠습니다.

    이전 단계에서 우리는 Action을 호출하기 위하여 TutorialListener 객체에서 Device 정보를 받아 PLT_CtrlPoint 객체에서 이 정보를 이용해 Action을 호출하였습니다. Event에서도 이와 비슷한 형태가 필요합니다. 그래서 이렇게 서로 다른 객체사이에 정보를 주고 받기 위한 복잡한 인터페이스를 줄이기 위헤, TutorialListener 클래스가 PLT_CtrlPoint 클래스와 PLT_CtrlPointListener 클래스를 다중 상속 받도록 변경하겠습니다. 그리고 TutorialListener 클래스의 이름을 TutorialCP로 변경합니다. 변경된 코드는 아래와 같습니다.


class TutorialCP : public PLT_CtrlPoint, public PLT_CtrlPointListener
{
public:
	TutorialCP(void)
	{
		AddListener(this);
	}
	~TutorialCP(void)
	{
	}

	// ...

	void Print(const char* str)
	{
		if (!device_.IsNull()) {
			PLT_Service* service;
			device_->FindServiceById("urn:upnp-org:serviceId:Tutorial.001", service);
			PLT_ActionDesc* desc = service->FindActionDesc("Print");
			PLT_ActionReference action(new PLT_Action(*desc));
			action->SetArgumentValue("String", str);
			InvokeAction(action);
		}
	}

	// ...
};


    클래스의 형태를 변경하면서 Listener 객체(여기에서는 자기 자신)를 생성자에서 바로 등록하도록 하였습니다. 그리고 device() 함수를 제거하고, main() 함수에서 콘솔의 입력을 받아 "Print" Action을 실행하던 부분을 Print() 함수로 묶었습니다.

    클래스 형태가 변하였으므로 main() 함수에서 TutorialCP(구 TutorialListener) 객체를 사용하는 코드에도 변경이 일어났습니다. 우선 Control-Point를 만들고 등록하는 부분은 아래와 같습니다.


TutorialCP* ctrlpoint = new TutorialCP;
PLT_CtrlPointReference ref(ctrlpoint);
upnp.AddCtrlPoint(ref);


    콘솔의 입력을 받아 "Print" Action을 호출하는 부분은 아래와 같이 변경되었습니다.


ctrlpoint->Print(input.c_str());


    빌드 후 실행하면 이전 단계와 동일하게 동작합니다. 이제 여기에서부터 Event에 대한 구현을 다시 시작합니다.

    Control-Point에서 Event를 받기 위해서는 Device Spy 프로그램에서 "Subscribe to Events"를 했듯이, PLT_CtrlPoint 객체서 Event를 받기 원하는 Service에 대해 Subscribe() 함수를 호출해야 합니다. 이 동작은 OnDeviceAdded() 함수에서 수행합니다. Service는 Action을 호출할 때 Print() 함수에서도 쓰이므로 멤버변수로 저장해 놓습니다. TutorialCP 클래스에서 변경된 코드들은 아래와 같습니다.


class TutorialCP : public PLT_CtrlPoint, public PLT_CtrlPointListener
{
public:
	// ...

	virtual NPT_Result OnDeviceAdded(PLT_DeviceDataReference& device)
	{
		if (device->GetFriendlyName() == "Tutorial Device") {
			printf("[Tutorial Device] added\n");
			device->FindServiceById("urn:upnp-org:serviceId:Tutorial.001", service_);
			Subscribe(service_);
			device_ = device;
		}
		return NPT_SUCCESS;
	}

	virtual NPT_Result OnDeviceRemoved(PLT_DeviceDataReference& device)
	{
		if (device->GetFriendlyName() == "Tutorial Device") {
			printf("[Tutorial Device] removed\n");
			service_ = 0;
			device_ = 0;
		}
		return NPT_SUCCESS;
	}

	// ...

	void Print(const char* str)
	{
		if (!device_.IsNull()) {
			PLT_ActionDesc* desc = service_->FindActionDesc("Print");
			// ...
		}
	}

private:
	// ...
	PLT_Service* service_;
};


    마지막으로 OnEventNotify() 함수에서 Event를 받아서 변경된 "PrintBuffer"의 값을 콘솔에 출력합니다. 코드는 아래와 같습니다.


virtual NPT_Result OnEventNotify(PLT_Service* service, NPT_List<plt_statevariable*>* vars)
{
	const PLT_DeviceData* device = service->GetDevice();
	if (device->GetFriendlyName() == "Tutorial Device") {
		NPT_List<plt_statevariable*>::Iterator itr = vars->GetFirstItem();
		for (bool stop = false ; !stop ; ++itr) {
			const PLT_StateVariable* state_variable = *itr;
			if (state_variable->GetName() == "PrintBuffer") {
				const NPT_String& value = state_variable->GetValue();
				printf("[PrintBuffer] changed: %s\n", value);
				break;
			}
			stop = (itr == vars->GetLastItem());
		}
	}
	return NPT_SUCCESS;
}


    빌드 후 실행시킨 후 Device의 콘솔에서 직접 값을 변경하거나 Control-Point의 Action 호출을 통해 값을 변경하였을 때, Event에 의해 Control-Point의 콘솔에 변경된 값이 출력되는 것을 볼 수 있습니다.


    이것으로 Platinum UPnP를 이용하여 UPnP의 기본 구성 요소인 Device와 Control-Point를 간단하게 구현해 보았습니다. Platinum UPnP는 매우 잘 설계된 강력한 라이브러리 이므로, 라이선스 문제가 있기 때문에 무조건 쓸 수 없겠지만, 사용할 수 있는 조건이 된다면 UPnP/DLNA 서비스를 만드는데 적극적으로 사용해 보기를 권장합니다.