Async UPnP Client
=================
Asyncio UPnP Client library for Python/asyncio.
Written initially for use in `Home Assistant <https://github.com/home-assistant/home-assistant>`_ to drive `DLNA DMR`-capable devices, but useful for other projects as well.
Status
------
.. image:: https://github.com/StevenLooman/async_upnp_client/workflows/Build/badge.svg
:target: https://github.com/StevenLooman/async_upnp_client/actions/workflows/ci-cd.yml
.. image:: https://img.shields.io/pypi/v/async_upnp_client.svg
:target: https://pypi.python.org/pypi/async_upnp_client
.. image:: https://img.shields.io/pypi/format/async_upnp_client.svg
:target: https://pypi.python.org/pypi/async_upnp_client
.. image:: https://img.shields.io/pypi/pyversions/async_upnp_client.svg
:target: https://pypi.python.org/pypi/async_upnp_client
.. image:: https://img.shields.io/pypi/l/async_upnp_client.svg
:target: https://pypi.python.org/pypi/async_upnp_client
General set up
--------------
The `UPnP Device Architecture <https://openconnectivity.org/upnp-specs/UPnP-arch-DeviceArchitecture-v2.0-20200417.pdf>`_ document contains several sections describing different parts of the UPnP standard. These chapters/sections can mostly be mapped to the following modules:
* Chapter 1 Discovery
* Section 1.1 SSDP: ``async_upnp_client.ssdp``
* Section 1.2 Advertisement: ``async_upnp_client.advertisement`` provides basic functionality to receive advertisements.
* Section 1.3 Search: ``async_upnp_client.search`` provides basic functionality to do search requests and gather the responses.
* ``async_upnp_client.ssdp_client`` contains the ``SsdpListener`` which 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 Control
* ``async_upnp_client.client_factory``/``async_upnp_client.client`` provide a series of classes to get information about the device/services using the 'description', and interact with these devices.
* ``async_upnp_client.server`` provides a series of classes to set up a UPnP server, including SSDP discovery/advertisements.
* Chapter 4 Eventing
* ``async_upnp_client.event_handler`` provides 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.igd``
* Digital Living Network Alliance (DLNA)
* ``async_upnp_client.profiles.dlna``
* Printers
* ``async_upnp_client.profiles.printer``
For examples on how to use ``async_upnp_client``, see ``examples``/ .
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.
Contributing
------------
See ``CONTRIBUTING.rst``.
Development
-----------
Development is done on the ``development`` branch.
``pre-commit`` is used to run several checks before committing. You can install ``pre-commit`` and the git-hook by doing::
$ pip install pre-commit
$ pre-commit --install
The `Open Connectivity Foundation <https://openconnectivity.org/>`_ provides a bundle with all `UPnP Specifications <https://openconnectivity.org/developer/specifications/upnp-resources/upnp/>`_.
Changes
-------
Changes are recorded using `Towncier <https://towncrier.readthedocs.io/>`_. Once a new release is created, towncrier is used to create the file ``CHANGES.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 the ``changes`` directory. Add a short description of the change to that file.
Releasing
---------
Steps for releasing:
- Switch to development: ``git checkout development``
- Do a pull: ``git pull``
- Run towncrier: ``towncrier build --version <version>``
- Commit towncrier results: ``git commit -m "Towncrier"``
- Run bump2version: ``bump2version --tag major/minor/patch``
- Note that this creates a new commit + tag.
- Push to github: ``git push && git push --tags``
Profiling
---------
To do profiling it is recommended to install `pytest-profiling <https://pypi.org/project/pytest-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.svg
Alternatively, you can generate a profiling data file, use `pyprof2calltree <https://github.com/pwaller/pyprof2calltree/>`_ to convert the data and open `kcachegrind <http://kcachegrind.sourceforge.net/html/Home.html>`_. 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 kcachegrind
upnp-client
-----------
A command line interface is provided via the ``upnp-client`` script. This script can be used to:
- call an action
- subscribe to services and listen for events
- show UPnP traffic (--debug-traffic) from and to the device
- show pretty printed JSON (--pprint) for human readability
- search for devices
- listen for advertisements
The 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 AVTransport
An 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 support
------------
IPv6 is supported for the UPnP client functionality as well as the SSDP functionality. Please do note that multicast over IPv6 does require a ``scope_id``/interface ID. The ``scope_id`` is used to specify which interface should be used.
There are several ways to get the ``scope_id``. Via Python this can be done via the `ifaddr <https://github.com/pydron/ifaddr>`_ library. From the (Linux) command line the ``scope_id`` can be found via the `ip` command::
$ 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 forever
In this case, the interface index is 6 (start of the line) and thus the ``scope_id`` is ``6``.
Or on Windows using the ``ipconfig`` command::
C:\> ipconfig /all
...
Ethernet adapter Ethernet:
...
Link-local IPv6 Address . . . . . : fe80::e530:c739:24d7:c8c7%8(Preferred)
...
The ``scope_id`` is ``8`` in this example, as shown after the ``%`` character at the end of the IPv6 address.
Or on macOS using the ``ifconfig`` command::
% 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
...
The ``scope_id`` is ``4`` in this example, as shown by ``scopeid 0x4``. Note that this is a hexadecimal value.
Be aware that Python ``<3.9`` does not support the ``IPv6Address.scope_id`` attribute. As such, a ``AddressTupleVXType`` is used to specify the ``source``- and ``target``-addresses. In case of IPv4, ``AddressTupleV4Type`` is a 2-tuple with ``address``, ``port``. ``AddressTupleV6Type`` is used for IPv6 and is a 4-tuple with ``address``, ``port``, ``flowinfo``, ``scope_id``. More information can be found in the Python ``socket`` module documentation.
All functionality regarding SSDP uses ``AddressTupleVXType`` the specify addresses.
For consistency, the ``AiohttpNotifyServer`` also uses a tuple the specify the ``source`` (the address and port the notify server listens on.)
Raw data
{
"_id": null,
"home_page": "https://github.com/StevenLooman/async_upnp_client",
"name": "async-upnp-client",
"maintainer": null,
"docs_url": null,
"requires_python": ">=3.8",
"maintainer_email": null,
"keywords": "ssdp, Simple Service Discovery Protocol, upnp, Universal Plug and Play",
"author": "Steven Looman",
"author_email": "steven.looman@gmail.com",
"download_url": "https://files.pythonhosted.org/packages/2b/07/03bb50b85f7b7b79e1a00d2261db8c28f3c39b8410a691b72385971c09ac/async_upnp_client-0.41.0.tar.gz",
"platform": null,
"description": "Async UPnP Client\n=================\n\nAsyncio UPnP Client library for Python/asyncio.\n\nWritten initially for use in `Home Assistant <https://github.com/home-assistant/home-assistant>`_ to drive `DLNA DMR`-capable devices, but useful for other projects as well.\n\nStatus\n------\n\n.. image:: https://github.com/StevenLooman/async_upnp_client/workflows/Build/badge.svg\n :target: https://github.com/StevenLooman/async_upnp_client/actions/workflows/ci-cd.yml\n\n.. image:: https://img.shields.io/pypi/v/async_upnp_client.svg\n :target: https://pypi.python.org/pypi/async_upnp_client\n\n.. image:: https://img.shields.io/pypi/format/async_upnp_client.svg\n :target: https://pypi.python.org/pypi/async_upnp_client\n\n.. image:: https://img.shields.io/pypi/pyversions/async_upnp_client.svg\n :target: https://pypi.python.org/pypi/async_upnp_client\n\n.. image:: https://img.shields.io/pypi/l/async_upnp_client.svg\n :target: https://pypi.python.org/pypi/async_upnp_client\n\n\nGeneral set up\n--------------\n\nThe `UPnP Device Architecture <https://openconnectivity.org/upnp-specs/UPnP-arch-DeviceArchitecture-v2.0-20200417.pdf>`_ document contains several sections describing different parts of the UPnP standard. These chapters/sections can mostly be mapped to the following modules:\n\n* Chapter 1 Discovery\n * Section 1.1 SSDP: ``async_upnp_client.ssdp``\n * Section 1.2 Advertisement: ``async_upnp_client.advertisement`` provides basic functionality to receive advertisements.\n * Section 1.3 Search: ``async_upnp_client.search`` provides basic functionality to do search requests and gather the responses.\n * ``async_upnp_client.ssdp_client`` contains the ``SsdpListener`` which 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.\n* Chapter 2 Description / Chapter 3 Control\n * ``async_upnp_client.client_factory``/``async_upnp_client.client`` provide a series of classes to get information about the device/services using the 'description', and interact with these devices.\n * ``async_upnp_client.server`` provides a series of classes to set up a UPnP server, including SSDP discovery/advertisements.\n* Chapter 4 Eventing\n * ``async_upnp_client.event_handler`` provides functionality to handle events received from the device.\n\nThere 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:\n\n* Internet Gateway Device (IGD)\n * ``async_upnp_client.profiles.igd``\n* Digital Living Network Alliance (DLNA)\n * ``async_upnp_client.profiles.dlna``\n* Printers\n * ``async_upnp_client.profiles.printer``\n\nFor examples on how to use ``async_upnp_client``, see ``examples``/ .\n\nNote 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.\n\n\nContributing\n------------\n\nSee ``CONTRIBUTING.rst``.\n\n\nDevelopment\n-----------\n\nDevelopment is done on the ``development`` branch.\n\n``pre-commit`` is used to run several checks before committing. You can install ``pre-commit`` and the git-hook by doing::\n\n $ pip install pre-commit\n $ pre-commit --install\n\nThe `Open Connectivity Foundation <https://openconnectivity.org/>`_ provides a bundle with all `UPnP Specifications <https://openconnectivity.org/developer/specifications/upnp-resources/upnp/>`_.\n\n\nChanges\n-------\n\nChanges are recorded using `Towncier <https://towncrier.readthedocs.io/>`_. Once a new release is created, towncrier is used to create the file ``CHANGES.rst``.\n\nTo create a new change run:\n\n $ towncrier create <pr-number>.<change type>\n\nA change type can be one of:\n\n- feature: Signifying a new feature.\n- bugfix: Signifying a bug fix.\n- doc: Signifying a documentation improvement.\n- removal: Signifying a deprecation or removal of public API.\n- misc: A ticket has been closed, but it is not of interest to users.\n\nA new file is then created in the ``changes`` directory. Add a short description of the change to that file.\n\n\n\nReleasing\n---------\n\nSteps for releasing:\n\n- Switch to development: ``git checkout development``\n- Do a pull: ``git pull``\n- Run towncrier: ``towncrier build --version <version>``\n- Commit towncrier results: ``git commit -m \"Towncrier\"``\n- Run bump2version: ``bump2version --tag major/minor/patch``\n - Note that this creates a new commit + tag.\n- Push to github: ``git push && git push --tags``\n\n\nProfiling\n---------\n\nTo do profiling it is recommended to install `pytest-profiling <https://pypi.org/project/pytest-profiling>`_. Then run a test with profiling enabled, and write the results to a graph::\n\n # Run tests with profiling and svg-output enabled. This will generate prof/*.prof files, and a svg file.\n $ pytest --profile-svg -k test_case_insensitive_dict_profile\n ...\n\n # Open generated SVG file.\n $ xdg-open prof/combined.svg\n\n\nAlternatively, you can generate a profiling data file, use `pyprof2calltree <https://github.com/pwaller/pyprof2calltree/>`_ to convert the data and open `kcachegrind <http://kcachegrind.sourceforge.net/html/Home.html>`_. For example::\n\n # Run tests with profiling enabled, this will generate prof/*.prof files.\n $ pytest --profile -k test_case_insensitive_dict_profile\n ...\n\n $ pyprof2calltree -i prof/combined.prof -k\n launching kcachegrind\n\n\nupnp-client\n-----------\n\nA command line interface is provided via the ``upnp-client`` script. This script can be used to:\n\n- call an action\n- subscribe to services and listen for events\n- show UPnP traffic (--debug-traffic) from and to the device\n- show pretty printed JSON (--pprint) for human readability\n- search for devices\n- listen for advertisements\n\nThe output of the script is a single line of JSON for each action-call or subscription-event. See the programs help for more information.\n\nAn example of calling an action::\n\n $ upnp-client --pprint call-action http://192.168.178.10:49152/description.xml RC/GetVolume InstanceID=0 Channel=Master\n {\n \"timestamp\": 1531482271.5603056,\n \"service_id\": \"urn:upnp-org:serviceId:RenderingControl\",\n \"service_type\": \"urn:schemas-upnp-org:service:RenderingControl:1\",\n \"action\": \"GetVolume\",\n \"in_parameters\": {\n \"InstanceID\": 0,\n \"Channel\": \"Master\"\n },\n \"out_parameters\": {\n \"CurrentVolume\": 70\n }\n }\n\n\nAn example of subscribing to all services, note that the program stays running until you stop it (ctrl-c)::\n\n $ upnp-client --pprint subscribe http://192.168.178.10:49152/description.xml \\*\n {\n \"timestamp\": 1531482518.3663802,\n \"service_id\": \"urn:upnp-org:serviceId:RenderingControl\",\n \"service_type\": \"urn:schemas-upnp-org:service:RenderingControl:1\",\n \"state_variables\": {\n \"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\"\n }\n }\n {\n \"timestamp\": 1531482518.366804,\n \"service_id\": \"urn:upnp-org:serviceId:RenderingControl\",\n \"service_type\": \"urn:schemas-upnp-org:service:RenderingControl:1\",\n \"state_variables\": {\n \"Mute\": false,\n \"Volume\": 70\n }\n }\n ...\n\nYou can subscribe to list of services by providing these names or abbreviated names, such as::\n\n $ upnp-client --pprint subscribe http://192.168.178.10:49152/description.xml RC AVTransport\n\n\nAn example of searching for devices::\n\n $ upnp-client --pprint search\n {\n \"Cache-Control\": \"max-age=3600\",\n \"Date\": \"Sat, 27 Oct 2018 10:43:42 GMT\",\n \"EXT\": \"\",\n \"Location\": \"http://192.168.178.1:49152/description.xml\",\n \"OPT\": \"\\\"http://schemas.upnp.org/upnp/1/0/\\\"; ns=01\",\n \"01-NLS\": \"906ad736-cfc4-11e8-9c22-8bb67c653324\",\n \"Server\": \"Linux/4.14.26+, UPnP/1.0, Portable SDK for UPnP devices/1.6.20.jfd5\",\n \"X-User-Agent\": \"redsonic\",\n \"ST\": \"upnp:rootdevice\",\n \"USN\": \"uuid:e3a17dd5-9d85-3131-3c34-b827eb498d72::upnp:rootdevice\",\n \"_timestamp\": \"2018-10-27 12:43:09.125408\",\n \"_host\": \"192.168.178.1\",\n \"_port\": 49152\n \"_udn\": \"uuid:e3a17dd5-9d85-3131-3c34-b827eb498d72\",\n \"_source\": \"search\"\n }\n\n\nAn example of listening for advertisements, note that the program stays running until you stop it (ctrl-c)::\n\n $ upnp-client --pprint advertisements\n {\n \"Host\": \"239.255.255.250:1900\",\n \"Cache-Control\": \"max-age=30\",\n \"Location\": \"http://192.168.178.1:1900/WFADevice.xml\",\n \"NTS\": \"ssdp:alive\",\n \"Server\": \"POSIX, UPnP/1.0 UPnP Stack/2013.4.3.0\",\n \"NT\": \"urn:schemas-wifialliance-org:device:WFADevice:1\",\n \"USN\": \"uuid:99cb221c-1f15-c620-dc29-395f415623c6::urn:schemas-wifialliance-org:device:WFADevice:1\",\n \"_timestamp\": \"2018-12-23 11:22:47.154293\",\n \"_host\": \"192.168.178.1\",\n \"_port\": 1900\n \"_udn\": \"uuid:99cb221c-1f15-c620-dc29-395f415623c6\",\n \"_source\": \"advertisement\"\n }\n\n\nIPv6 support\n------------\n\nIPv6 is supported for the UPnP client functionality as well as the SSDP functionality. Please do note that multicast over IPv6 does require a ``scope_id``/interface ID. The ``scope_id`` is used to specify which interface should be used.\n\nThere are several ways to get the ``scope_id``. Via Python this can be done via the `ifaddr <https://github.com/pydron/ifaddr>`_ library. From the (Linux) command line the ``scope_id`` can be found via the `ip` command::\n\n $ ip address\n ...\n 6: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP group default qlen 1000\n link/ether 00:15:5d:38:97:cf brd ff:ff:ff:ff:ff:ff\n inet 192.168.1.2/24 brd 192.168.1.255 scope global eth0\n valid_lft forever preferred_lft forever\n inet6 fe80::215:5dff:fe38:97cf/64 scope link\n valid_lft forever preferred_lft forever\n\nIn this case, the interface index is 6 (start of the line) and thus the ``scope_id`` is ``6``.\n\nOr on Windows using the ``ipconfig`` command::\n\n C:\\> ipconfig /all\n ...\n Ethernet adapter Ethernet:\n ...\n Link-local IPv6 Address . . . . . : fe80::e530:c739:24d7:c8c7%8(Preferred)\n ...\n\nThe ``scope_id`` is ``8`` in this example, as shown after the ``%`` character at the end of the IPv6 address.\n\nOr on macOS using the ``ifconfig`` command::\n\n % ifconfig\n ...\n en0: flags=8863<UP,BROADCAST,SMART,RUNNING,SIMPLEX,MULTICAST> mtu 1500\n options=50b<RXCSUM,TXCSUM,VLAN_HWTAGGING,AV,CHANNEL_IO>\n ether 38:c9:86:30:fe:be\n inet6 fe80::215:5dff:fe38:97cf%en0 prefixlen 64 secured scopeid 0x4\n ...\n\nThe ``scope_id`` is ``4`` in this example, as shown by ``scopeid 0x4``. Note that this is a hexadecimal value.\n\nBe aware that Python ``<3.9`` does not support the ``IPv6Address.scope_id`` attribute. As such, a ``AddressTupleVXType`` is used to specify the ``source``- and ``target``-addresses. In case of IPv4, ``AddressTupleV4Type`` is a 2-tuple with ``address``, ``port``. ``AddressTupleV6Type`` is used for IPv6 and is a 4-tuple with ``address``, ``port``, ``flowinfo``, ``scope_id``. More information can be found in the Python ``socket`` module documentation.\n\nAll functionality regarding SSDP uses ``AddressTupleVXType`` the specify addresses.\n\nFor consistency, the ``AiohttpNotifyServer`` also uses a tuple the specify the ``source`` (the address and port the notify server listens on.)\n",
"bugtrack_url": null,
"license": "Apache 2",
"summary": "Async UPnP Client",
"version": "0.41.0",
"project_urls": {
"GitHub: repo": "https://github.com/StevenLooman/async_upnp_client",
"Homepage": "https://github.com/StevenLooman/async_upnp_client"
},
"split_keywords": [
"ssdp",
" simple service discovery protocol",
" upnp",
" universal plug and play"
],
"urls": [
{
"comment_text": "",
"digests": {
"blake2b_256": "1f8f1a4a26b8492232119d973c76a0c22a67c56f60606273eb7d4045e0a5bb0c",
"md5": "1dc09c675a4d0d03312c3a9833b70a5a",
"sha256": "1438a0495a936f1651739127f73d52577cf955f7fa0171b13d218e0eb103a2b6"
},
"downloads": -1,
"filename": "async_upnp_client-0.41.0-py3-none-any.whl",
"has_sig": false,
"md5_digest": "1dc09c675a4d0d03312c3a9833b70a5a",
"packagetype": "bdist_wheel",
"python_version": "py3",
"requires_python": ">=3.8",
"size": 90332,
"upload_time": "2024-10-05T12:28:46",
"upload_time_iso_8601": "2024-10-05T12:28:46.737968Z",
"url": "https://files.pythonhosted.org/packages/1f/8f/1a4a26b8492232119d973c76a0c22a67c56f60606273eb7d4045e0a5bb0c/async_upnp_client-0.41.0-py3-none-any.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": "",
"digests": {
"blake2b_256": "2b0703bb50b85f7b7b79e1a00d2261db8c28f3c39b8410a691b72385971c09ac",
"md5": "ba24444a53a55f67bf46ca94d1de40c1",
"sha256": "06f1ec722fb04fd79f16ca5615260b3c3d958ff39e0163bc49c6ef0843606266"
},
"downloads": -1,
"filename": "async_upnp_client-0.41.0.tar.gz",
"has_sig": false,
"md5_digest": "ba24444a53a55f67bf46ca94d1de40c1",
"packagetype": "sdist",
"python_version": "source",
"requires_python": ">=3.8",
"size": 98412,
"upload_time": "2024-10-05T12:28:48",
"upload_time_iso_8601": "2024-10-05T12:28:48.172368Z",
"url": "https://files.pythonhosted.org/packages/2b/07/03bb50b85f7b7b79e1a00d2261db8c28f3c39b8410a691b72385971c09ac/async_upnp_client-0.41.0.tar.gz",
"yanked": false,
"yanked_reason": null
}
],
"upload_time": "2024-10-05 12:28:48",
"github": true,
"gitlab": false,
"bitbucket": false,
"codeberg": false,
"github_user": "StevenLooman",
"github_project": "async_upnp_client",
"travis_ci": false,
"coveralls": false,
"github_actions": true,
"tox": true,
"lcname": "async-upnp-client"
}