Switch to side-by-side view

--- a/upmpd/upmpd.cxx
+++ b/upmpd/upmpd.cxx
@@ -19,15 +19,20 @@
 
 #include <stdio.h>
 #include <stdlib.h>
+#include <math.h>
 
 #include <string>
 #include <iostream>
 #include <vector>
+#include <functional>
+
 using namespace std;
+using namespace std::placeholders;
 
 #include "libupnpp/upnpplib.hxx"
 #include "libupnpp/vdir.hxx"
 #include "libupnpp/soaphelp.hxx"
+#include "libupnpp/device.hxx"
 
 #include "mpdcli.hxx"
 
@@ -49,7 +54,7 @@
 "    <modelNumber>1.0</modelNumber>\n"
 "    <modelURL>http://www.github.com/medoc92/upmpd</modelURL>\n"
 "    <serialNumber>72</serialNumber>\n"
-"    <UDN>uuid:\n"
+"    <UDN>uuid:"
 	;
 static const string deviceDescription2 = 
 "</UDN>\n"
@@ -250,46 +255,6 @@
 "        </argument>\n"
 "      </argumentList>\n"
 "    </action>\n"
-"    <action>\n"
-"      <name>GetLoudness</name>\n"
-"      <argumentList>\n"
-"        <argument>\n"
-"          <name>InstanceID</name>\n"
-"          <direction>in</direction>\n"
-"          <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>\n"
-"        </argument>\n"
-"        <argument>\n"
-"          <name>Channel</name>\n"
-"          <direction>in</direction>\n"
-"          <relatedStateVariable>A_ARG_TYPE_Channel</relatedStateVariable>\n"
-"        </argument>\n"
-"        <argument>\n"
-"          <name>CurrentLoudness</name>\n"
-"          <direction>out</direction>\n"
-"          <relatedStateVariable>Loudness</relatedStateVariable>\n"
-"        </argument>\n"
-"      </argumentList>\n"
-"    </action>\n"
-"    <action>\n"
-"      <name>SetLoudness</name>\n"
-"      <argumentList>\n"
-"        <argument>\n"
-"          <name>InstanceID</name>\n"
-"          <direction>in</direction>\n"
-"          <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>\n"
-"        </argument>\n"
-"        <argument>\n"
-"          <name>Channel</name>\n"
-"          <direction>in</direction>\n"
-"          <relatedStateVariable>A_ARG_TYPE_Channel</relatedStateVariable>\n"
-"        </argument>\n"
-"        <argument>\n"
-"          <name>DesiredLoudness</name>\n"
-"          <direction>in</direction>\n"
-"          <relatedStateVariable>Loudness</relatedStateVariable>\n"
-"        </argument>\n"
-"      </argumentList>\n"
-"    </action>\n"
 "  </actionList>\n"
 "  <serviceStateTable>\n"
 "    <stateVariable sendEvents=\"no\">\n"
@@ -309,7 +274,7 @@
 "      <dataType>ui2</dataType>\n"
 "      <allowedValueRange>\n"
 "        <minimum>0</minimum>\n"
-"        <maximum>Vendor defined</maximum>\n"
+"        <maximum>100</maximum>\n"
 "        <step>1</step>\n"
 "      </allowedValueRange>\n"
 "    </stateVariable>\n"
@@ -317,33 +282,16 @@
 "      <name>VolumeDB</name>\n"
 "      <dataType>i2</dataType>\n"
 "      <allowedValueRange>\n"
-"        <minimum>Vendor defined</minimum>\n"
-"        <maximum>Vendor defined</maximum>\n"
-"        <step>Vendor defined</step>\n"
+"        <minimum>-10240</minimum>\n"
+"        <maximum>0</maximum>\n"
+"        <step>1</step>\n"
 "      </allowedValueRange>\n"
-"    </stateVariable>\n"
-"    <stateVariable sendEvents=\"no\">\n"
-"      <name>Loudness</name>\n"
-"      <dataType>boolean</dataType>\n"
 "    </stateVariable>\n"
 "    <stateVariable sendEvents=\"no\">\n"
 "      <name>A_ARG_TYPE_Channel</name>\n"
 "      <dataType>string</dataType>\n"
 "      <allowedValueList>\n"
 "        <allowedValue>Master</allowedValue>\n"
-"        <allowedValue>LF</allowedValue>\n"
-"        <allowedValue>RF</allowedValue>\n"
-"        <allowedValue>CF</allowedValue>\n"
-"        <allowedValue>LFE</allowedValue>\n"
-"        <allowedValue>LS</allowedValue>\n"
-"        <allowedValue>RS</allowedValue>\n"
-"        <allowedValue>LFC</allowedValue>\n"
-"        <allowedValue>RFC</allowedValue>\n"
-"        <allowedValue>SD</allowedValue>\n"
-"        <allowedValue>SL</allowedValue>\n"
-"        <allowedValue>SR </allowedValue>\n"
-"        <allowedValue>T</allowedValue>\n"
-"        <allowedValue>B</allowedValue>\n"
 "      </allowedValueList>\n"
 "    </stateVariable>\n"
 "    <stateVariable sendEvents=\"no\">\n"
@@ -355,82 +303,265 @@
 "      <dataType>string</dataType>\n"
 "      <allowedValueList>\n"
 "        <allowedValue>FactoryDefaults</allowedValue>\n"
-"        <allowedValue>InstallationDefaults</allowedValue>\n"
-"        <allowedValue>Vendor defined</allowedValue>\n"
 "      </allowedValueList>\n"
 "    </stateVariable>\n"
 "  </serviceStateTable>\n"
 "</scpd>\n"
 ;
 
-
-typedef int (*soapfun)(const SoapCall&, MPDCli *) ;
-static map<string, soapfun> soapcalls;
-
-int setMute(const SoapCall& sc, MPDCli *mpdcli)
-{
-	map<string, string>::const_iterator it = sc.args.find("DesiredMute");
+// We do db upnp-encoded values from -10240 (0%) to 0 (100%)
+static int percentodbvalue(int value)
+{
+    int dbvalue;
+    if (value == 0) {
+        dbvalue = -10240;
+    } else {
+        float ratio = float(value)*value / 10000.0;
+        float db = 10 * log10(ratio);
+        dbvalue = int(256 * db);
+    }
+    return dbvalue;
+}
+
+static int dbvaluetopercent(int dbvalue)
+{
+    float db = float(dbvalue) / 256.0;
+    float vol = exp10(db/10);
+    int percent = floor(sqrt(vol * 10000.0));
+	if (percent < 0)	percent = 0;
+	if (percent > 100)	percent = 100;
+    return percent;
+}
+
+class UpMpd : public UpnpDevice {
+public:
+	UpMpd(const string& deviceid, MPDCli *mpdcli);
+
+	int setMute(const SoapArgs& sc, SoapData& data);
+	int getMute(const SoapArgs& sc, SoapData& data);
+	int setVolume(const SoapArgs& sc, SoapData& data, bool isDb);
+	int getVolume(const SoapArgs& sc, SoapData& data, bool isDb);
+	int listPresets(const SoapArgs& sc, SoapData& data);
+	int selectPreset(const SoapArgs& sc, SoapData& data);
+	int getVolumeDBRange(const SoapArgs& sc, SoapData& data);
+    virtual bool getEventData(std::vector<std::string>& names, 
+                              std::vector<std::string>& values);
+	
+private:
+	void doNotifyEvent();
+	MPDCli *m_mpdcli;
+};
+
+
+UpMpd::UpMpd(const string& deviceid, MPDCli *mpdcli)
+	: UpnpDevice(deviceid), m_mpdcli(mpdcli)
+{
+	addServiceType("urn:upnp-org:serviceId:RenderingControl",
+				   "urn:schemas-upnp-org:service:RenderingControl:1");
+
+	{
+		auto bound = bind(&UpMpd::setMute, this, _1, _2);
+		addActionMapping("SetMute", bound);
+	}
+	{
+		auto bound = bind(&UpMpd::getMute, this, _1, _2);
+		addActionMapping("GetMute", bound);
+	}
+	{
+		auto bound = bind(&UpMpd::setVolume, this, _1, _2, false);
+		addActionMapping("SetVolume", bound);
+	}
+	{
+		auto bound = bind(&UpMpd::setVolume, this, _1, _2, true);
+		addActionMapping("SetVolumeDB", bound);
+	}
+	{
+		auto bound = bind(&UpMpd::getVolume, this, _1, _2, false);
+		addActionMapping("GetVolume", bound);
+	}
+	{
+		auto bound = bind(&UpMpd::getVolume, this, _1, _2, true);
+		addActionMapping("GetVolumeDB", bound);
+	}
+	{
+		auto bound = bind(&UpMpd::listPresets, this, _1, _2);
+		addActionMapping("ListPresets", bound);
+	}
+	{
+		auto bound = bind(&UpMpd::selectPreset, this, _1, _2);
+		addActionMapping("SelectPreset", bound);
+	}
+	{
+		auto bound = bind(&UpMpd::getVolumeDBRange, this, _1, _2);
+		addActionMapping("GetVolumeDBRange", bound);
+	}
+}
+
+// LastChange contains all the variables that were changed since the last
+// event. For us that's at most Mute, Volume, VolumeDB
+// <Event xmlns=���urn:schemas-upnp-org:metadata-1-0/AVT_RCS">
+//   <InstanceID val=���0���>
+//     <Mute channel=���Master��� val=���0���/>
+//     <Volume channel=���Master��� val=���24���/>
+//     <VolumeDB channel=���Master��� val=���24���/>
+//   </InstanceID>
+// </Event>
+bool UpMpd::getEventData(std::vector<std::string>& names, 
+						 std::vector<std::string>& values)
+{
+	names.push_back("LastChange");
+	int volume = m_mpdcli->getVolume();
+	char cvalue[40];
+	string 
+		chgdata("<Event xmlns=\"urn:schemas-upnp-org:metadata-1-0/AVT_RCS\">\n"
+				"<InstanceID val=\"0\">\n"
+				"<Mute channel=\"Master\" val=\"");
+    chgdata += volume == 0 ? "1" : "0";
+	chgdata += "\"/>\n<Volume channel=\"Master\" val=\"";
+	sprintf(cvalue, "%d", volume);
+	chgdata += cvalue;
+	chgdata += "\"/>\n<VolumeDB channel=\"Master\" val=\"";
+	sprintf(cvalue, "%d", percentodbvalue(volume));
+	chgdata += cvalue;
+	chgdata += "\"/>\n</InstanceID>\n</Event>";
+	values.push_back(chgdata);
+	cerr << "UpMpd::getEventData: " << chgdata << endl;
+	return true;
+}
+	
+void UpMpd::doNotifyEvent()
+{
+	std::vector<std::string> names;
+	std::vector<std::string> values;
+	getEventData(names, values);
+	notifyEvent("urn:upnp-org:serviceId:RenderingControl", names, values);
+}
+
+int UpMpd::getVolumeDBRange(const SoapArgs& sc, SoapData& data)
+{
+	map<string, string>::const_iterator it;
+
+	it = sc.args.find("Channel");
+	if (it == sc.args.end() || it->second.compare("Master")) {
+		return UPNP_E_INVALID_PARAM;
+	}
+	data.data.push_back(pair<string,string>("MinValue", "-10240"));
+	data.data.push_back(pair<string,string>("MaxValue", "0"));
+
+	return UPNP_E_SUCCESS;
+}
+
+int UpMpd::setMute(const SoapArgs& sc, SoapData& data)
+{
+	map<string, string>::const_iterator it;
+
+	it = sc.args.find("Channel");
+	if (it == sc.args.end() || it->second.compare("Master")) {
+		return UPNP_E_INVALID_PARAM;
+	}
+		
+	it = sc.args.find("DesiredMute");
 	if (it == sc.args.end() || it->second.empty()) {
 		return UPNP_E_INVALID_PARAM;
 	}
-	if (it->second[0] == 'F') {
+	if (it->second[0] == 'F' || it->second[0] == '0') {
 		// Relative set of 0 -> restore pre-mute
-		mpdcli->setVolume(0, 1);
-	} else if (it->second[0] == 'T') {
-		mpdcli->setVolume(0);
+		m_mpdcli->setVolume(0, 1);
+	} else if (it->second[0] == 'T' || it->second[0] == '1') {
+		m_mpdcli->setVolume(0);
 	} else {
 		return UPNP_E_INVALID_PARAM;
 	}
-}
-
-static PTMutexInit cblock;
-static int cluCallBack(Upnp_EventType et, void* evp, void* token)
-{
-	PTMutexLocker lock(cblock);
-	MPDCli *mpdcli = (MPDCli*)token;
-
-	cerr << "cluCallBack: evt type:" << 
-		LibUPnP::evTypeAsString(et).c_str() << endl;
-
-	switch (et) {
-	case UPNP_CONTROL_ACTION_REQUEST:
-	{
-		struct Upnp_Action_Request *act = (struct Upnp_Action_Request *)evp;
-		cerr << "UPNP_CONTROL_ACTION_REQUEST: " << act->ActionName <<
-			" Params: " << ixmlPrintDocument(act->ActionRequest) << endl;
-		SoapCall sc;
-		if (!decodeSoap(act->ActionName, act->ActionRequest, &sc)) {
-			return UPNP_E_INVALID_PARAM;
-		}
-		map<string, soapfun>::iterator it = soapcalls.find(sc.name);
-		if (it != soapcalls.end())
-			return it->second(sc, mpdcli);
-		else
-			return UPNP_E_INVALID_PARAM;
-	}
-	break;
-	case UPNP_CONTROL_GET_VAR_REQUEST:
-	{
-		struct Upnp_State_Var_Request *act = 
-			(struct Upnp_State_Var_Request *)evp;
-		cerr << "UPNP_CONTROL_ACTION_REQUEST: " << act->StateVarName << endl;
-	}
-	break;
-	case UPNP_EVENT_SUBSCRIPTION_REQUEST:
-	{
-		struct Upnp_Subscription_Request *act = 
-			(struct  Upnp_Subscription_Request*)evp;
-	}
-	break;
-	default:
-		// Ignore other events for now
-		break;
-	}
-
-	return UPNP_E_INVALID_PARAM;
-}
-
-
+	doNotifyEvent();
+	return UPNP_E_SUCCESS;
+}
+
+int UpMpd::getMute(const SoapArgs& sc, SoapData& data)
+{
+	map<string, string>::const_iterator it;
+
+	it = sc.args.find("Channel");
+	if (it == sc.args.end() || it->second.compare("Master")) {
+		return UPNP_E_INVALID_PARAM;
+	}
+	int volume = m_mpdcli->getVolume();
+	data.data.push_back(pair<string,string>("CurrentMute", 
+											volume == 0 ? "1" : "0"));
+	return UPNP_E_SUCCESS;
+}
+
+int UpMpd::setVolume(const SoapArgs& sc, SoapData& data, bool isDb)
+{
+	map<string, string>::const_iterator it;
+
+	it = sc.args.find("Channel");
+	if (it == sc.args.end() || it->second.compare("Master")) {
+		return UPNP_E_INVALID_PARAM;
+	}
+		
+	it = sc.args.find("DesiredVolume");
+	if (it == sc.args.end() || it->second.empty()) {
+		return UPNP_E_INVALID_PARAM;
+	}
+	int volume = atoi(it->second.c_str());
+	if (isDb) {
+		volume = dbvaluetopercent(volume);
+	} 
+	if (volume < 0 || volume > 100) {
+		return UPNP_E_INVALID_PARAM;
+	}
+	m_mpdcli->setVolume(volume);
+	doNotifyEvent();
+	return UPNP_E_SUCCESS;
+}
+
+int UpMpd::getVolume(const SoapArgs& sc, SoapData& data, bool isDb)
+{
+	map<string, string>::const_iterator it;
+
+	it = sc.args.find("Channel");
+	if (it == sc.args.end() || it->second.compare("Master")) {
+		return UPNP_E_INVALID_PARAM;
+	}
+		
+	int volume = m_mpdcli->getVolume();
+	if (isDb) {
+		volume = percentodbvalue(volume);
+	}
+	char svolume[30];
+	sprintf(svolume, "%d", volume);
+	data.data.push_back(pair<string,string>("CurrentVolume", svolume));
+	return UPNP_E_SUCCESS;
+}
+
+int UpMpd::listPresets(const SoapArgs& sc, SoapData& data)
+{
+	map<string, string>::const_iterator it;
+
+	// The 2nd arg is a comma-separated list of preset names
+	data.data.push_back(pair<string,string>("CurrentPresetNameList",
+											"FactoryDefaults"));
+	return UPNP_E_SUCCESS;
+}
+
+int UpMpd::selectPreset(const SoapArgs& sc, SoapData& data)
+{
+	map<string, string>::const_iterator it;
+		
+	it = sc.args.find("PresetName");
+	if (it == sc.args.end() || it->second.empty()) {
+		return UPNP_E_INVALID_PARAM;
+	}
+	if (it->second.compare("FactoryDefaults")) {
+		return UPNP_E_INVALID_PARAM;
+	}
+
+	// Well there is only the volume actually...
+	m_mpdcli->setVolume(50);
+
+	doNotifyEvent();
+	return UPNP_E_SUCCESS;
+}
 
 static char *thisprog;
 static char usage [] =
@@ -455,8 +586,6 @@
 	if (argc != 0)
 		Usage();
 
-	soapcalls["SetMute"] = setMute;
-
 	LibUPnP *mylib = LibUPnP::getLibUPnP(true);
 	if (!mylib) {
 		cerr << " Can't get LibUPnP" << endl;
@@ -469,23 +598,23 @@
 	}
 	mylib->setLogFileName("/tmp/clilibupnp.log");
 
-	MPDCli mpdcli("rasp1.dockes.com");
+	MPDCli mpdcli("hm1.dockes.com");
 	if (!mpdcli.ok()) {
 		cerr << "MPD connection failed" << endl;
 		return 1;
 	}
-	mylib->registerHandler(UPNP_CONTROL_ACTION_REQUEST, cluCallBack, &mpdcli);
-	mylib->registerHandler(UPNP_CONTROL_GET_VAR_REQUEST, cluCallBack, &mpdcli);
-	mylib->registerHandler(UPNP_EVENT_SUBSCRIPTION_REQUEST, cluCallBack, &mpdcli);
 
 	myDeviceUUID = LibUPnP::makeDevUUID(friendlyName);
-	string description = deviceDescription1 + myDeviceUUID + deviceDescription2;
-	cerr << myDeviceUUID << endl;
+	cerr << "Generated UUID: [" << myDeviceUUID << "]" << endl;
+
+	UpMpd device(string("uuid:") + myDeviceUUID, &mpdcli);
+
 	VirtualDir* theVD = VirtualDir::getVirtualDir();
 	if (theVD == 0) {
 		cerr << "Can't get VirtualDir" << endl;
 		return 1;
 	}
+	string description = deviceDescription1 + myDeviceUUID + deviceDescription2;
 	theVD->addFile("/", "description.xml", description, "application/xml");
 	theVD->addFile("/", "RenderingControl.xml", scdp, "application/xml");