Universal Plug and Play.


A C++ library for managing UPnP devices and services using the Sming framework.


A separate UPnP Schema is used to manage UPnP device and service schema. It generates C++ classes and sample code directly from these, which you can then use in your application.

Generation of suitable schema can be done using the Device Scanner tool.

Controlling devices

The Basic ControlPoint sample shows how this is done.


To control UPnP-enabled devices on your local network they must first be located.

In order for this to happen, the framework must be able to match the C++ class implementation against the schema.

The data structures are generated by the UPnP Schema, which creates a function registerClasses for each domain. For example, UPnP::schemas_upnp_org::registerClasses() registers all devices and services for the standard namespace.

Alternatively a subset of classes may be registered by calling UPnP::ControlPoint::registerClasses() directly.

Here’s an example from the ControlPoint sample:

#include <Network/UPnP/schemas-upnp-org/ClassGroup.h>

// Let's make things a little easier for ourselves
using namespace UPnP::schemas_upnp_org::device;
using namespace UPnP::schemas_upnp_org::service;

void initUPnP()

This is done using a UPnP::ControlPoint::beginSearch() method, which takes two parameters: the first identifies what you are looking for, the second is a callback which gets invoked when a match has been found. You’ll typically implement this callback using a lambda function.

For example, let’s find all MediaRenderer1 devices:

// Only one active search is permitted so be sure to cancel any existing ones first
controlPoint.beginSearch(Delegate<bool(MediaRenderer1&)>([](auto& device) {
   // We can now do stuff with the located device

   // Return true to keep the device, false to destroy it
   return false;

This method takes a template parameter which is the C++ class type defining the device you are searching for. The framework will fetch the description for each corresponding device and construct a UPnP::DeviceControl object with appropriate services and embedded devices.


Your search callback function gets a reference to a located device. These devices are created on the heap and owned by the UPnP::ControlPoint. If you want to keep the device, you should take a reference to it and return true from the callback.

To actually do anything useful typically requires use of a UPnP::ServiceControl object. You’ll usually get this by calling UPnP::DeviceControl::getService<UPnP::ObjectClass>() or one of the generated helper methods. Note that this returns a pointer, which will be nullptr if the service isn’t available:

auto render = device.getRenderingControl();
if(render != nullptr) {
   // ...

Once you have a Service object, you can control it using action methods:

render->getVolume(0, RenderingControl1::Channel::fs_Master, [&device](auto response) {
   // Process response here

Action methods take a list of zero or more input parameters, with the final argument for the response.


The exact type of the response can be determined for you by the compiler. Here’s the explicit call:

render->getVolume(0, RenderingControl1::Channel::fs_Master, [&device](RenderingControl1::GetVolume::Response response response) {
   // ...

OK, so handling the action method response. You can get the result values using methods of response, but you must first check that the device did not return a fault:

Serial.println(_F("render->getVolume(0, Master):"));
// Sample uses a `checkResponse` helper function
if(auto fault = response.fault()) {
} else {
   Serial.print(_F(": Current Volume = "));

Implementing devices

The Basic UPnP sample contains a couple of examples of how to create your own hosted devices. The TeaPot device is the simplest possible implementation, with no services.

The Wemo device is more elaborate and has two services.

Both of these are constructed using code generated from custom schema. These are located in the project’s schema directory which is picked up automatically when the UPnP Schema library is built.

The framework generates a class template for each device and service. For example, take a look in samples/Basic_UPnP/include/Wemo.h#L59-L91:

class BasicEventService : public service::basicevent1Template<BasicEventService>
   // Need access to constructors
   using basicevent1Template::basicevent1Template;

   // Override methods if you need to customise any fields
   String getField(Field desc) const override
      switch(desc) {
      case Field::serviceId:
         // You could also put this in the schema
         return F("urn:Belkin:serviceId:basicevent1");
         return basicevent1Template::getField(desc);

   // Access to our device implementation
   Controllee& controllee()
      return reinterpret_cast<Controllee&>(device());

   /* Here are the action methods */

   Error getBinaryState(GetBinaryState::Response response)
      return Error::Success;

   Error setBinaryState(bool state, SetBinaryState::Response response)
      return Error::Success;

This perhaps slightly strange construction uses CRTP to use static polymorphism and avoid virtual method tables. This allows the compiler to generate more efficient code.

UPnP Tools



Under Ubuntu Linux you can install gupnp-tools:

sudo apt install gupnp-tools

And then discover devices on the local network using the following command:


You can also start a “software” smart bulb device and use it to test your control point application:



Used by

SoC support

  • esp32

  • esp32c3

  • esp32s2

  • esp32s3

  • esp8266

  • host

  • rp2040