File: device_updater.py

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 (128 lines) | stat: -rw-r--r-- 4,491 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
# -*- coding: utf-8 -*-
"""async_upnp_client.device_updater module."""

import logging
from typing import Optional

from async_upnp_client.advertisement import SsdpAdvertisementListener
from async_upnp_client.client import UpnpDevice
from async_upnp_client.client_factory import UpnpFactory
from async_upnp_client.const import AddressTupleVXType
from async_upnp_client.utils import CaseInsensitiveDict

_LOGGER = logging.getLogger(__name__)


class DeviceUpdater:
    """
    Device updater.

    Listens for SSDP advertisements and updates device inline when needed.
    Inline meaning that it keeps the original UpnpDevice instance.
    So be sure to keep only references to the UpnpDevice,
    as a device might decide to remove a service after an update!
    """

    def __init__(
        self,
        device: UpnpDevice,
        factory: UpnpFactory,
        source: Optional[AddressTupleVXType] = None,
    ) -> None:
        """Initialize."""
        self._device = device
        self._factory = factory
        self._listener = SsdpAdvertisementListener(
            async_on_alive=self._async_on_alive,
            async_on_byebye=self._async_on_byebye,
            async_on_update=self._async_on_update,
            source=source,
        )

    async def async_start(self) -> None:
        """Start listening for notifications."""
        _LOGGER.debug("Start listening for notifications.")
        await self._listener.async_start()

    async def async_stop(self) -> None:
        """Stop listening for notifications."""
        _LOGGER.debug("Stop listening for notifications.")
        await self._listener.async_stop()

    async def _async_on_alive(self, headers: CaseInsensitiveDict) -> None:
        """Handle on alive."""
        # Ensure for root devices only.
        if headers.get("nt") != "upnp:rootdevice":
            return

        # Ensure for our device.
        if headers.get("_udn") != self._device.udn:
            return

        _LOGGER.debug("Handling alive: %s", headers)
        await self._async_handle_alive_update(headers)

    async def _async_on_byebye(self, headers: CaseInsensitiveDict) -> None:
        """Handle on byebye."""
        _LOGGER.debug("Handling on_byebye: %s", headers)
        self._device.available = False

    async def _async_on_update(self, headers: CaseInsensitiveDict) -> None:
        """Handle on update."""
        # Ensure for root devices only.
        if headers.get("nt") != "upnp:rootdevice":
            return

        # Ensure for our device.
        if headers.get("_udn") != self._device.udn:
            return

        _LOGGER.debug("Handling update: %s", headers)
        await self._async_handle_alive_update(headers)

    async def _async_handle_alive_update(self, headers: CaseInsensitiveDict) -> None:
        """Handle on_alive or on_update."""
        do_reinit = False

        # Handle BOOTID.UPNP.ORG.
        boot_id = headers.get("BOOTID.UPNP.ORG")
        device_boot_id = self._device.ssdp_headers.get("BOOTID.UPNP.ORG")
        if boot_id and boot_id != device_boot_id:
            _LOGGER.debug("New boot_id: %s, old boot_id: %s", boot_id, device_boot_id)
            do_reinit = True

        # Handle CONFIGID.UPNP.ORG.
        config_id = headers.get("CONFIGID.UPNP.ORG")
        device_config_id = self._device.ssdp_headers.get("CONFIGID.UPNP.ORG")
        if config_id and config_id != device_config_id:
            _LOGGER.debug(
                "New config_id: %s, old config_id: %s",
                config_id,
                device_config_id,
            )
            do_reinit = True

        # Handle LOCATION.
        location = headers.get("LOCATION")
        if location and self._device.device_url != location:
            _LOGGER.debug(
                "New location: %s, old location: %s", location, self._device.device_url
            )
            do_reinit = True

        if location and do_reinit:
            await self._reinit_device(location, headers)

        # We heard from it, so mark it available.
        self._device.available = True

    async def _reinit_device(
        self, location: str, ssdp_headers: CaseInsensitiveDict
    ) -> None:
        """Reinitialize device."""
        # pylint: disable=protected-access
        _LOGGER.debug("Reinitializing device, location: %s", location)

        new_device = await self._factory.async_create_device(location)
        self._device.reinit(new_device)
        self._device.ssdp_headers = ssdp_headers