|
a |
|
b/src/myrdcvolume.cpp |
|
|
1 |
// This libupnpp sample does about the same thing as rdcvolume, but it defines
|
|
|
2 |
// its own control classes instead of the ones predefined by libupnpp.
|
|
|
3 |
|
|
|
4 |
#include <termios.h>
|
|
|
5 |
|
|
|
6 |
#include <string>
|
|
|
7 |
#include <iostream>
|
|
|
8 |
#include <mutex>
|
|
|
9 |
#include <condition_variable>
|
|
|
10 |
#include <functional>
|
|
|
11 |
|
|
|
12 |
#include "libupnpp/upnpplib.hxx"
|
|
|
13 |
#include "libupnpp/soaphelp.hxx"
|
|
|
14 |
#include "libupnpp/control/discovery.hxx"
|
|
|
15 |
#include "libupnpp/control/device.hxx"
|
|
|
16 |
#include "libupnpp/control/service.hxx"
|
|
|
17 |
|
|
|
18 |
using namespace std;
|
|
|
19 |
using namespace std::placeholders;
|
|
|
20 |
|
|
|
21 |
//using namespace UPnPClient;
|
|
|
22 |
//using namespace UPnPP;
|
|
|
23 |
|
|
|
24 |
|
|
|
25 |
// Locally defined control class for the Rendering Control
|
|
|
26 |
// service. We're just copying code from the libupnpp actually, this is
|
|
|
27 |
// just to show that it can be done outside the library.
|
|
|
28 |
class MyRDC : public UPnPClient::Service {
|
|
|
29 |
public:
|
|
|
30 |
|
|
|
31 |
/* Construct by copying data from device and service objects.*/
|
|
|
32 |
MyRDC(const UPnPClient::UPnPDeviceDesc& device,
|
|
|
33 |
const UPnPClient::UPnPServiceDesc& service)
|
|
|
34 |
: UPnPClient::Service(device, service) {
|
|
|
35 |
|
|
|
36 |
// We want to have a look at our service description file
|
|
|
37 |
// (xml) to retrieve the min/max/step values for the
|
|
|
38 |
// volume. Not all services need to do this.
|
|
|
39 |
UPnPClient::UPnPServiceDesc::Parsed sdesc;
|
|
|
40 |
if (service.fetchAndParseDesc(device.URLBase, sdesc)) {
|
|
|
41 |
auto it = sdesc.stateTable.find("Volume");
|
|
|
42 |
if (it != sdesc.stateTable.end() && it->second.hasValueRange) {
|
|
|
43 |
m_volmin = it->second.minimum;
|
|
|
44 |
m_volmax = it->second.maximum;
|
|
|
45 |
m_volstep = it->second.step;
|
|
|
46 |
} else {
|
|
|
47 |
// ??
|
|
|
48 |
m_volmin = 0;
|
|
|
49 |
m_volmax = 100;
|
|
|
50 |
m_volstep = 1;
|
|
|
51 |
}
|
|
|
52 |
}
|
|
|
53 |
|
|
|
54 |
// Say that we want to receive events. This is not mandatory
|
|
|
55 |
registerCallback();
|
|
|
56 |
}
|
|
|
57 |
|
|
|
58 |
virtual ~MyRDC() {
|
|
|
59 |
unregisterCallback();
|
|
|
60 |
}
|
|
|
61 |
|
|
|
62 |
/* Test that a service type matches ours. This can be used
|
|
|
63 |
with the directory traversal routine */
|
|
|
64 |
static bool isRDCService(const std::string& st) {
|
|
|
65 |
// Note that we do not care about the version
|
|
|
66 |
return st.find("urn:schemas-upnp-org:service:RenderingControl") == 0;
|
|
|
67 |
}
|
|
|
68 |
|
|
|
69 |
int setVolume(int volume, const std::string& channel = "Master");
|
|
|
70 |
int getVolume(const std::string& channel = "Master");
|
|
|
71 |
|
|
|
72 |
/* Volume settings params */
|
|
|
73 |
int m_volmin;
|
|
|
74 |
int m_volmax;
|
|
|
75 |
int m_volstep;
|
|
|
76 |
|
|
|
77 |
private:
|
|
|
78 |
|
|
|
79 |
void evtCallback(const unordered_map<string, string>& props) {
|
|
|
80 |
// The callback gets a map of changed properties as
|
|
|
81 |
// parameter. In turn, the classes defined by libupnpp
|
|
|
82 |
// (e.g. RenderingControl) call a client event reporter in an
|
|
|
83 |
// uniform way, and after massaging the data a bit, but you
|
|
|
84 |
// can do whatever you like here. UPnP AV is special
|
|
|
85 |
// because it coalesces the values inside a LastChange XML
|
|
|
86 |
// string. Many services just report them individually.
|
|
|
87 |
cerr << "evtCallback: props size " << props.size() << endl;
|
|
|
88 |
for (const auto& ent : props) {
|
|
|
89 |
cout << ent.first << " -> " << ent.second << endl;
|
|
|
90 |
}
|
|
|
91 |
}
|
|
|
92 |
|
|
|
93 |
// Register our member function callback. It's just an
|
|
|
94 |
// std::function, other approaches may be possible.
|
|
|
95 |
void registerCallback() {
|
|
|
96 |
UPnPClient::Service::registerCallback(
|
|
|
97 |
std::bind(&MyRDC::evtCallback, this, _1));
|
|
|
98 |
}
|
|
|
99 |
};
|
|
|
100 |
|
|
|
101 |
// The libupnpp equivalent checks and converts the range, and also
|
|
|
102 |
// that a volume change is actually required, and does appropriate
|
|
|
103 |
// rounding. We're just showing how to send a parameter here. The arg
|
|
|
104 |
// names are defined by the service description XML file, so it would
|
|
|
105 |
// be possible to construct the call after the XML data (a la
|
|
|
106 |
// upnp-inspector), there is nothing in libupnpp to prevent it.
|
|
|
107 |
int MyRDC::setVolume(int ivol, const string& channel)
|
|
|
108 |
{
|
|
|
109 |
// Outgoing parameters. The object is constructed with the service
|
|
|
110 |
// type (comes from the description we were built on), and the
|
|
|
111 |
// action name. This is sufficient for some actions (ie stop())
|
|
|
112 |
UPnPP::SoapOutgoing args(getServiceType(), "SetVolume");
|
|
|
113 |
|
|
|
114 |
// This call needs further outgoing arguments, which goes in there
|
|
|
115 |
// through an operator() overload
|
|
|
116 |
args("InstanceID", "0")("Channel", channel)
|
|
|
117 |
("DesiredVolume", UPnPP::SoapHelp::i2s(ivol));
|
|
|
118 |
|
|
|
119 |
// We have to declare a return parameter, even if we don't care
|
|
|
120 |
// about the contents.
|
|
|
121 |
UPnPP::SoapIncoming data;
|
|
|
122 |
|
|
|
123 |
return runAction(args, data);
|
|
|
124 |
}
|
|
|
125 |
|
|
|
126 |
// Same as setVolume really, except that we look at the return data.
|
|
|
127 |
int MyRDC::getVolume(const string& channel)
|
|
|
128 |
{
|
|
|
129 |
UPnPP::SoapOutgoing args(getServiceType(), "GetVolume");
|
|
|
130 |
args("InstanceID", "0")("Channel", channel);
|
|
|
131 |
UPnPP::SoapIncoming data;
|
|
|
132 |
int ret = runAction(args, data);
|
|
|
133 |
if (ret != UPNP_E_SUCCESS) {
|
|
|
134 |
return ret;
|
|
|
135 |
}
|
|
|
136 |
int volume;
|
|
|
137 |
if (!data.get("CurrentVolume", &volume)) {
|
|
|
138 |
cerr << "MyRDC:getVolume: missing CurrentVolume in response\n";
|
|
|
139 |
return UPNP_E_BAD_RESPONSE;
|
|
|
140 |
}
|
|
|
141 |
|
|
|
142 |
return volume;
|
|
|
143 |
}
|
|
|
144 |
|
|
|
145 |
|
|
|
146 |
// Device discovery part. We do it the easy way here: use a blocking
|
|
|
147 |
// call which will wait for the initial window to complete. We could
|
|
|
148 |
// traverse the device directory in search, for example of a device of
|
|
|
149 |
// a specific kind instead of using a device name like we do here (there is
|
|
|
150 |
// an example of UPnPDeviceDirectory::traverse() usage in uplistdir.cpp).
|
|
|
151 |
//
|
|
|
152 |
// See rdcvolume.cpp for a version using callbacks to get the device asap
|
|
|
153 |
shared_ptr<MyRDC> getService(const string& name)
|
|
|
154 |
{
|
|
|
155 |
// Initialize and get a discovery directory handle.
|
|
|
156 |
auto *superdir = UPnPClient::UPnPDeviceDirectory::getTheDir();
|
|
|
157 |
if (nullptr == superdir) {
|
|
|
158 |
cerr << "Discovery init failed\n";
|
|
|
159 |
return shared_ptr<MyRDC>();
|
|
|
160 |
}
|
|
|
161 |
|
|
|
162 |
UPnPClient::UPnPDeviceDesc devicedesc;
|
|
|
163 |
// We look-up the device by either friendlyname or udn as the 2
|
|
|
164 |
// namespaces are unlikely to overlap, no need to complicate things
|
|
|
165 |
if (!superdir->getDevByUDN(name, devicedesc) &&
|
|
|
166 |
!superdir->getDevByFName(name, devicedesc)) {
|
|
|
167 |
cerr << "Can't connect to " << name << endl;
|
|
|
168 |
return shared_ptr<MyRDC>();
|
|
|
169 |
}
|
|
|
170 |
|
|
|
171 |
// UPnPClient::Device does nothing really interesting actually. It
|
|
|
172 |
// just holds the device description. Derived device
|
|
|
173 |
// implementations, for example for a MediaRenderer, add a bit of
|
|
|
174 |
// value by creating objects for the well-known services. Here we
|
|
|
175 |
// just dispense with the device creation, and directly create a
|
|
|
176 |
// service object.
|
|
|
177 |
|
|
|
178 |
// Walk the device description service list, looking for ours
|
|
|
179 |
for (const auto& ent : devicedesc.services) {
|
|
|
180 |
if (MyRDC::isRDCService(ent.serviceType)) {
|
|
|
181 |
cout << ent.dump() << endl;
|
|
|
182 |
return make_shared<MyRDC>(devicedesc, ent);
|
|
|
183 |
}
|
|
|
184 |
}
|
|
|
185 |
cerr << name << " has no rendering control service\n";
|
|
|
186 |
return shared_ptr<MyRDC>();
|
|
|
187 |
}
|
|
|
188 |
|
|
|
189 |
// nothing to see here: character reading, one at a time.
|
|
|
190 |
int mygetch()
|
|
|
191 |
{
|
|
|
192 |
struct termios oldt, newt;
|
|
|
193 |
int ch;
|
|
|
194 |
tcgetattr(STDIN_FILENO, &oldt);
|
|
|
195 |
newt = oldt;
|
|
|
196 |
newt.c_lflag &= ~(ICANON | ECHO);
|
|
|
197 |
tcsetattr(STDIN_FILENO, TCSANOW, &newt);
|
|
|
198 |
ch = getchar();
|
|
|
199 |
tcsetattr(STDIN_FILENO, TCSANOW, &oldt);
|
|
|
200 |
return ch;
|
|
|
201 |
}
|
|
|
202 |
|
|
|
203 |
int main(int argc, char **argv)
|
|
|
204 |
{
|
|
|
205 |
argv++;argc--;
|
|
|
206 |
if (argc != 1) {
|
|
|
207 |
cerr << "Usage: rdcvolume rendererNameOrUid\n";
|
|
|
208 |
return 1;
|
|
|
209 |
}
|
|
|
210 |
string devname(*argv++);
|
|
|
211 |
argc--;
|
|
|
212 |
|
|
|
213 |
// Initialize libupnpp logging
|
|
|
214 |
Logger::getTheLog("")->setLogLevel(Logger::LLERR);
|
|
|
215 |
|
|
|
216 |
// Explicitely initialize libupnpp so that we can display a
|
|
|
217 |
// possible error
|
|
|
218 |
UPnPP::LibUPnP *mylib = UPnPP::LibUPnP::getLibUPnP();
|
|
|
219 |
if (!mylib) {
|
|
|
220 |
cerr << "Can't get LibUPnP" << endl;
|
|
|
221 |
return 1;
|
|
|
222 |
}
|
|
|
223 |
if (!mylib->ok()) {
|
|
|
224 |
cerr << "Lib init failed: " <<
|
|
|
225 |
mylib->errAsString("main", mylib->getInitError()) << endl;
|
|
|
226 |
return 1;
|
|
|
227 |
}
|
|
|
228 |
|
|
|
229 |
|
|
|
230 |
shared_ptr<MyRDC> rdc = getService(devname);
|
|
|
231 |
if (!rdc) {
|
|
|
232 |
cerr << "Device " << devname <<
|
|
|
233 |
" has no RenderingControl service" << endl;
|
|
|
234 |
return 1;
|
|
|
235 |
}
|
|
|
236 |
|
|
|
237 |
cout << "q = quit, 'u' = up, 'd' = down\n";
|
|
|
238 |
for (;;) {
|
|
|
239 |
int vol = rdc->getVolume();
|
|
|
240 |
cout << "Volume now " << vol << endl;
|
|
|
241 |
int key = mygetch();
|
|
|
242 |
|
|
|
243 |
if (key == 'q') {
|
|
|
244 |
cout << "QUIT\n";
|
|
|
245 |
break;
|
|
|
246 |
} else if (key == 'u') {
|
|
|
247 |
vol += 5;
|
|
|
248 |
if (vol > 100) {
|
|
|
249 |
vol = 100;
|
|
|
250 |
}
|
|
|
251 |
} else if (key == 'd') {
|
|
|
252 |
vol -= 5;
|
|
|
253 |
if (vol < 0) {
|
|
|
254 |
vol = 0;
|
|
|
255 |
}
|
|
|
256 |
} else {
|
|
|
257 |
cout << "Bad key: " << (char)key << endl;
|
|
|
258 |
continue;
|
|
|
259 |
}
|
|
|
260 |
if (rdc->setVolume(vol)) {
|
|
|
261 |
cerr << "setVolume(" << vol << ") failed\n";
|
|
|
262 |
}
|
|
|
263 |
}
|
|
|
264 |
|
|
|
265 |
return 0;
|
|
|
266 |
}
|
|
|
267 |
|