Child: [8a6060] (diff)

Download this file

myrdcvolume.cpp    268 lines (229 with data), 8.9 kB

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