Switch to unified view

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