--- a
+++ b/hello_src/helloctl.cpp
@@ -0,0 +1,144 @@
+// This libupnpp sample is the control side for the hellodevice sample
+// device implementation
+
+#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;
+
+// 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 HelloCTL : public UPnPClient::Service {
+public:
+
+    /* Construct by copying data from device and service objects.*/
+    HelloCTL(const UPnPClient::UPnPDeviceDesc& device,
+          const UPnPClient::UPnPServiceDesc& service)
+        : UPnPClient::Service(device, service) {
+        // No event handling. Look at, e.g. rdcvolume.cpp for an example
+    }
+
+    virtual ~HelloCTL() {  }
+
+    /* Test that a service type matches ours. This can be used
+       with the directory traversal routine */
+    static bool isHelloService(const std::string& st) {
+        // Note that we do not care about the version
+        return st.find("urn:upnpp-schemas:service:HelloService") == 0;
+    }
+
+    int hello();
+};
+
+int HelloCTL::hello()
+{
+    // 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(), "Hello");
+
+    // This call does not need further outgoing arguments (see
+    // rdcvolume for an example of these.
+
+    // We have to declare a return parameter, even if we don't care
+    // about the contents.
+    UPnPP::SoapIncoming data;
+
+    int ret = runAction(args, data);
+    if (ret != UPNP_E_SUCCESS) {
+        return ret;
+    }
+    string value;
+    if (!data.get("MyValue", &value)) {
+        cerr << "HelloCTL:getVolume: missing MyValue in response\n";
+        return UPNP_E_BAD_RESPONSE;
+    }
+
+    cout << "Hello : " << value << endl;
+    return 0;
+}
+
+// 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<HelloCTL> getService(const string& name)
+{
+    // Initialize and get a discovery directory handle.
+    auto *superdir = UPnPClient::UPnPDeviceDirectory::getTheDir(1);
+    if (nullptr == superdir) {
+        cerr << "Discovery init failed\n";
+        return shared_ptr<HelloCTL>();
+    }
+
+    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<HelloCTL>();
+    }
+
+    // Walk the device description service list, looking for ours
+    for (const auto& ent : devicedesc.services) {
+        // cout << ent.dump() << endl;
+        if (HelloCTL::isHelloService(ent.serviceType)) {
+            return make_shared<HelloCTL>(devicedesc, ent);
+        }
+    }
+    cerr << name << " has no hello service\n";
+    return shared_ptr<HelloCTL>();
+}
+
+int main(int argc, char **argv)
+{
+    // Initialize libupnpp logging
+    Logger::getTheLog("")->setLogLevel(Logger::LLERR);
+
+    argv++;argc--;
+    if (argc != 1) {
+        cerr << "Usage: rdcvolume rendererNameOrUid\n";
+        return 1;
+    }
+    string devname(*argv++); 
+    argc--;
+    
+
+    // 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<HelloCTL> hlo = getService(devname);
+    if (!hlo) {
+        cerr << "Device " << devname << 
+            " has no Hello service" << endl;
+        return 1;
+    }
+    hlo->hello();
+    return 0;
+}
+