Package: async-upnp-client
Asyncio UPnP Client library for Python/asyncio.Written initially for use inHome Assistantto driveDLNA DMR-capable devices, but useful for other projects as well.StatusGeneral set upTheUPnP Device Architecturedocument contains several sections describing different parts of the UPnP standard. These chapters/sections can mostly be mapped to the following modules:Chapter 1 DiscoverySection 1.1 SSDP:async_upnp_client.ssdpSection 1.2 Advertisement:async_upnp_client.advertisementprovides basic functionality to receive advertisements.Section 1.3 Search:async_upnp_client.searchprovides basic functionality to do search requests and gather the responses.async_upnp_client.ssdp_clientcontains theSsdpListenerwhich combines advertisements and search to get the known devices and provides callbacks on changes. It is meant as something which runs continuously to provide useful information about the SSDP-active devices.Chapter 2 Description / Chapter 3 Controlasync_upnp_client.client_factory/async_upnp_client.clientprovide a series of classes to get information about the device/services using the ‘description’, and interact with these devices.async_upnp_client.serverprovides a series of classes to set up a UPnP server, including SSDP discovery/advertisements.Chapter 4 Eventingasync_upnp_client.event_handlerprovides functionality to handle events received from the device.There are several ‘profiles’ which a device can implement to provide a standard interface to talk to. Some of these profiles are added to this library. The following profiles are currently available:Internet Gateway Device (IGD)async_upnp_client.profiles.igdDigital Living Network Alliance (DLNA)async_upnp_client.profiles.dlnaPrintersasync_upnp_client.profiles.printerFor examples on how to useasync_upnp_client, seeexamples/ .Note that this library is most likely does not fully implement all functionality from the UPnP Device Architecture document and/or contains errors/bugs/mis-interpretations.ContributingSeeCONTRIBUTING.rst.DevelopmentDevelopment is done on thedevelopmentbranch.pre-commitis used to run several checks before committing. You can installpre-commitand the git-hook by doing:$ pip install pre-commit
$ pre-commit --installTheOpen Connectivity Foundationprovides a bundle with allUPnP Specifications.ChangesChanges are recorded usingTowncier. Once a new release is created, towncrier is used to create the fileCHANGES.rst.To create a new change run:$ towncrier create <pr-number>.<change type>A change type can be one of:feature: Signifying a new feature.bugfix: Signifying a bug fix.doc: Signifying a documentation improvement.removal: Signifying a deprecation or removal of public API.misc: A ticket has been closed, but it is not of interest to users.A new file is then created in thechangesdirectory. Add a short description of the change to that file.ReleasingSteps for releasing:Switch to development:git checkout developmentDo a pull:git pullRun towncrier:towncrier build--version<version>Commit towncrier results:git commit-m"Towncrier"Run bump2version:bump2version--tagmajor/minor/patchNote that this creates a new commit + tag.Checkout tag:git checkout <version>Build:rm-rfbuild dist && python setup.py build sdistUpload using twine:twine upload dist/*Switch to development:git checkout developmentPush to github:git push && git push--tagsProfilingTo do profiling it is recommended to installpytest-profiling. Then run a test with profiling enabled, and write the results to a graph:# Run tests with profiling and svg-output enabled. This will generate prof/*.prof files, and a svg file.
$ pytest --profile-svg -k test_case_insensitive_dict_profile
...

# Open generated SVG file.
$ xdg-open prof/combined.svgAlternatively, you can generate a profiling data file, usepyprof2calltreeto convert the data and openkcachegrind. For example:# Run tests with profiling enabled, this will generate prof/*.prof files.
$ pytest --profile -k test_case_insensitive_dict_profile
...

$ pyprof2calltree -i prof/combined.prof -k
launching kcachegrindupnp-clientA command line interface is provided via theupnp-clientscript. This script can be used to:call an actionsubscribe to services and listen for eventsshow UPnP traffic (–debug-traffic) from and to the deviceshow pretty printed JSON (–pprint) for human readabilitysearch for deviceslisten for advertisementsThe output of the script is a single line of JSON for each action-call or subscription-event. See the programs help for more information.An example of calling an action:$ upnp-client --pprint call-action http://192.168.178.10:49152/description.xml RC/GetVolume InstanceID=0 Channel=Master
{
    "timestamp": 1531482271.5603056,
    "service_id": "urn:upnp-org:serviceId:RenderingControl",
    "service_type": "urn:schemas-upnp-org:service:RenderingControl:1",
    "action": "GetVolume",
    "in_parameters": {
        "InstanceID": 0,
        "Channel": "Master"
    },
    "out_parameters": {
        "CurrentVolume": 70
    }
}An example of subscribing to all services, note that the program stays running until you stop it (ctrl-c):$ upnp-client --pprint subscribe http://192.168.178.10:49152/description.xml \*
{
    "timestamp": 1531482518.3663802,
    "service_id": "urn:upnp-org:serviceId:RenderingControl",
    "service_type": "urn:schemas-upnp-org:service:RenderingControl:1",
    "state_variables": {
        "LastChange": "<Event xmlns=\"urn:schemas-upnp-org:metadata-1-0/AVT_RCS\">\n<InstanceID val=\"0\">\n<Mute channel=\"Master\" val=\"0\"/>\n<Volume channel=\"Master\" val=\"70\"/>\n</InstanceID>\n</Event>\n"
    }
}
{
    "timestamp": 1531482518.366804,
    "service_id": "urn:upnp-org:serviceId:RenderingControl",
    "service_type": "urn:schemas-upnp-org:service:RenderingControl:1",
    "state_variables": {
        "Mute": false,
        "Volume": 70
    }
}
...You can subscribe to list of services by providing these names or abbreviated names, such as:$ upnp-client --pprint subscribe http://192.168.178.10:49152/description.xml RC AVTransportAn example of searching for devices:$ upnp-client --pprint search
{
    "Cache-Control": "max-age=3600",
    "Date": "Sat, 27 Oct 2018 10:43:42 GMT",
    "EXT": "",
    "Location": "http://192.168.178.1:49152/description.xml",
    "OPT": "\"http://schemas.upnp.org/upnp/1/0/\"; ns=01",
    "01-NLS": "906ad736-cfc4-11e8-9c22-8bb67c653324",
    "Server": "Linux/4.14.26+, UPnP/1.0, Portable SDK for UPnP devices/1.6.20.jfd5",
    "X-User-Agent": "redsonic",
    "ST": "upnp:rootdevice",
    "USN": "uuid:e3a17dd5-9d85-3131-3c34-b827eb498d72::upnp:rootdevice",
    "_timestamp": "2018-10-27 12:43:09.125408",
    "_host": "192.168.178.1",
    "_port": 49152
    "_udn": "uuid:e3a17dd5-9d85-3131-3c34-b827eb498d72",
    "_source": "search"
}An example of listening for advertisements, note that the program stays running until you stop it (ctrl-c):$ upnp-client --pprint advertisements
{
    "Host": "239.255.255.250:1900",
    "Cache-Control": "max-age=30",
    "Location": "http://192.168.178.1:1900/WFADevice.xml",
    "NTS": "ssdp:alive",
    "Server": "POSIX, UPnP/1.0 UPnP Stack/2013.4.3.0",
    "NT": "urn:schemas-wifialliance-org:device:WFADevice:1",
    "USN": "uuid:99cb221c-1f15-c620-dc29-395f415623c6::urn:schemas-wifialliance-org:device:WFADevice:1",
    "_timestamp": "2018-12-23 11:22:47.154293",
    "_host": "192.168.178.1",
    "_port": 1900
    "_udn": "uuid:99cb221c-1f15-c620-dc29-395f415623c6",
    "_source": "advertisement"
}IPv6 supportIPv6 is supported for the UPnP client functionality as well as the SSDP functionality. Please do note that multicast over IPv6 does require ascope_id/interface ID. Thescope_idis used to specify which interface should be used.There are several ways to get thescope_id. Via Python this can be done via theifaddrlibrary. From the (Linux) command line thescope_idcan be found via theipcommand:$ ip address
...
6: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP group default qlen 1000
    link/ether 00:15:5d:38:97:cf brd ff:ff:ff:ff:ff:ff
    inet 192.168.1.2/24 brd 192.168.1.255 scope global eth0
        valid_lft forever preferred_lft forever
    inet6 fe80::215:5dff:fe38:97cf/64 scope link
        valid_lft forever preferred_lft foreverIn this case, the interface index is 6 (start of the line) and thus thescope_idis6.Or on Windows using theipconfigcommand:C:\> ipconfig /all
...
Ethernet adapter Ethernet:
    ...
    Link-local IPv6 Address . . . . . : fe80::e530:c739:24d7:c8c7%8(Preferred)
...Thescope_idis8in this example, as shown after the%character at the end of the IPv6 address.Or on macOS using theifconfigcommand:% ifconfig
...
en0: flags=8863<UP,BROADCAST,SMART,RUNNING,SIMPLEX,MULTICAST> mtu 1500
      options=50b<RXCSUM,TXCSUM,VLAN_HWTAGGING,AV,CHANNEL_IO>
      ether 38:c9:86:30:fe:be
      inet6 fe80::215:5dff:fe38:97cf%en0 prefixlen 64 secured scopeid 0x4
...Thescope_idis4in this example, as shown byscopeid 0x4. Note that this is a hexadecimal value.Be aware that Python<3.9does not support theIPv6Address.scope_idattribute. As such, aAddressTupleVXTypeis used to specify thesource- andtarget-addresses. In case of IPv4,AddressTupleV4Typeis a 2-tuple withaddress,port.AddressTupleV6Typeis used for IPv6 and is a 4-tuple withaddress,port,flowinfo,scope_id. More information can be found in the Pythonsocketmodule documentation.All functionality regarding SSDP usesAddressTupleVXTypethe specify addresses.For consistency, theAiohttpNotifyServeralso uses a tuple the specify thesource(the address and port the notify server listens on.)