File: README.rst

package info (click to toggle)
async-upnp-client 0.44.0-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 1,072 kB
  • sloc: python: 11,921; xml: 2,826; sh: 32; makefile: 6
file content (277 lines) | stat: -rw-r--r-- 11,712 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
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 (note that this creates a new commit + tag): ``bump2version --tag major/minor/patch``
- 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.)