Example #1
0
    async def _async_get_url_xml(self, url: str) -> ET.Element:
        """Fetch device description."""
        status_code, _, response_body = \
            await self.requester.async_http_request('GET', url)

        if status_code != 200:
            raise UpnpError("Received status code: {}".format(status_code))

        root = DET.fromstring(response_body)  # type: ET.Element
        return root
Example #2
0
    def _state_variable_parse_xml(self, state_variable_xml: ET.Element) -> StateVariableInfo:
        """Parse XML for state variable."""
        # pylint: disable=no-self-use

        # send events
        send_events = False
        if 'sendEvents' in state_variable_xml.attrib:
            send_events = state_variable_xml.attrib['sendEvents'] == 'yes'
        elif state_variable_xml.find('service:sendEventsAttribute', NS) is not None:
            send_events = \
                state_variable_xml.findtext('service:sendEventsAttribute', None, NS) == 'yes'
        else:
            _LOGGER.debug('Invalid XML for state variable/send events:\n%s',
                          ET.tostring(state_variable_xml, encoding='unicode'))

        # data type
        data_type = state_variable_xml.findtext('service:dataType', None, NS)
        if data_type not in STATE_VARIABLE_TYPE_MAPPING:
            raise UpnpError('Unsupported data type: %s' % (data_type, ))
        data_type = data_type
        data_type_python = STATE_VARIABLE_TYPE_MAPPING[data_type]

        # default value
        default_value = state_variable_xml.findtext('service:defaultValue', None, NS)

        # allowed value ranges
        allowed_value_range = {}  # type: Dict[str, Optional[str]]
        allowed_value_range_el = state_variable_xml.find('service:allowedValueRange', NS)
        if allowed_value_range_el is not None:
            allowed_value_range = {
                'min': allowed_value_range_el.findtext('service:minimum', None, NS),
                'max': allowed_value_range_el.findtext('service:maximum', None, NS),
                'step': allowed_value_range_el.findtext('service:step', None, NS),
            }

        # allowed value list
        allowed_values = None  # type: Optional[List[str]]
        allowed_value_list_el = state_variable_xml.find('service:allowedValueList', NS)
        if allowed_value_list_el is not None:
            allowed_values = \
                [v.text for v in allowed_value_list_el.findall('service:allowedValue', NS)
                 if v.text is not None]

        type_info = StateVariableTypeInfo(data_type=data_type,
                                          data_type_python=data_type_python,
                                          default_value=default_value,
                                          allowed_value_range=allowed_value_range,
                                          allowed_values=allowed_values,
                                          xml=state_variable_xml)
        name = state_variable_xml.findtext('service:name', '', NS).strip()
        return StateVariableInfo(name=name,
                                 send_events=send_events,
                                 type_info=type_info,
                                 xml=state_variable_xml)
Example #3
0
    async def async_resubscribe(
        self,
        service: "UpnpService",
        timeout: timedelta = timedelta(seconds=1800),
    ) -> Tuple[bool, Optional[str], Optional[timedelta]]:
        """Renew subscription to a UpnpService."""
        _LOGGER.debug("Resubscribing to: %s", service)

        # do SUBSCRIBE request
        sid = self.sid_for_service(service)
        if not sid:
            raise UpnpError("Could not find SID for service")

        headers = {
            "HOST": urllib.parse.urlparse(service.event_sub_url).netloc,
            "SID": sid,
            "TIMEOUT": "Second-" + str(timeout.seconds),
        }
        response_status, response_headers, _ = await self._requester.async_http_request(
            "SUBSCRIBE", service.event_sub_url, headers)

        # check results
        if response_status != 200:
            _LOGGER.debug("Did not receive 200, but %s", response_status)
            return False, None, None

        # Devices should return the SID when re-subscribe,
        # but in case it doesn't, use the new SID.
        if "sid" in response_headers and response_headers["sid"]:
            new_sid: str = response_headers["sid"]
            if new_sid != sid:
                del self._subscriptions[sid]
                sid = new_sid

        # Device can give a different TIMEOUT header than what we have provided.
        new_timeout = timeout
        if ("timeout" in response_headers
                and response_headers["timeout"] != "Second-infinite"
                and "Second-" in response_headers["timeout"]):
            response_timeout = response_headers["timeout"]
            timeout_seconds = int(response_timeout[7:])  # len("Second-") == 7
            new_timeout = timedelta(seconds=timeout_seconds)

        renewal_time = datetime.now() + new_timeout
        self._subscriptions[sid] = SubscriptionInfo(
            service=service,
            timeout=timeout,
            renewal_time=renewal_time,
        )
        _LOGGER.debug("Got SID: %s, renewal_time: %s", sid, renewal_time)

        return True, sid, new_timeout
Example #4
0
    async def _async_get_url_xml(self, url: str) -> ET.Element:
        """Fetch device description."""
        status_code, _, response_body = await self.requester.async_http_request(
            "GET", url
        )

        if status_code != 200:
            raise UpnpError("Received status code: {}".format(status_code))

        description: str = (response_body or "").rstrip(" \t\r\n\0")  # type: ignore
        try:
            element: ET.Element = DET.fromstring(description)
            return element
        except ET.ParseError as err:
            _LOGGER.debug("Unable to parse XML: %s\nXML:\n%s", err, description)
            raise
Example #5
0
    async def async_resubscribe(
        self,
        service: 'UpnpService',
        timeout: timedelta = timedelta(seconds=1800)  # noqa: E252
    ) -> Tuple[bool, Optional[str]]:
        """Renew subscription to a UpnpService."""
        _LOGGER.debug('Resubscribing to: %s', service)

        # do SUBSCRIBE request
        sid = self.sid_for_service(service)
        if not sid:
            raise UpnpError('Could not find SID for service')

        headers = {
            'HOST': urllib.parse.urlparse(service.event_sub_url).netloc,
            'SID': sid,
            'TIMEOUT': 'Second-' + str(timeout.seconds),
        }
        response_status, response_headers, _ = \
            await self._requester.async_http_request('SUBSCRIBE', service.event_sub_url, headers)

        # check results
        if response_status != 200:
            _LOGGER.debug('Did not receive 200, but %s', response_status)
            return False, None

        # Devices should return the SID when re-subscribe,
        # but in case it doesn't, use the new SID.
        if 'sid' in response_headers and response_headers['sid']:
            new_sid: str = response_headers['sid']
            if new_sid != sid:
                del self._subscriptions[sid]
                sid = new_sid

        renewal_time = datetime.now() + timeout
        self._subscriptions[sid] = SubscriptionInfo(
            service=service,
            timeout=timeout,
            renewal_time=renewal_time,
        )
        _LOGGER.debug('Got SID: %s, renewal_time: %s', sid, renewal_time)

        return True, sid
Example #6
0
    async def _reinit_device(self, location: Optional[str],
                             boot_id: Optional[str],
                             config_id: Optional[str]) -> None:
        """Reinitialize device."""
        # pylint: disable=protected-access
        _LOGGER.debug("Reinitializing device")

        if location is None:
            raise UpnpError("Should never happen")

        new_device = await self._factory.async_create_device(location)

        self._device._device_info = new_device._device_info

        # Copy new services and update the binding between the original device and new services.
        self._device.services = new_device.services
        for service in self._device.services.values():
            service.device = self._device

        self._device.boot_id = boot_id
        self._device.config_id = config_id
    def _state_variable_parse_xml(
        self, state_variable_xml: ET.Element
    ) -> StateVariableInfo:
        """Parse XML for state variable."""
        # pylint: disable=no-self-use

        # send events
        send_events = False
        if "sendEvents" in state_variable_xml.attrib:
            send_events = state_variable_xml.attrib["sendEvents"] == "yes"
        elif state_variable_xml.find("service:sendEventsAttribute", NS) is not None:
            send_events = (
                state_variable_xml.findtext("service:sendEventsAttribute", None, NS)
                == "yes"
            )
        else:
            _LOGGER.debug(
                "Invalid XML for state variable/send events:\n%s",
                ET.tostring(state_variable_xml, encoding="unicode"),
            )

        # data type
        data_type = state_variable_xml.findtext("service:dataType", None, NS)
        if data_type is None or data_type not in STATE_VARIABLE_TYPE_MAPPING:
            raise UpnpError("Unsupported data type: %s" % (data_type,))
        data_type_mapping = STATE_VARIABLE_TYPE_MAPPING[data_type]

        # default value
        default_value = state_variable_xml.findtext("service:defaultValue", None, NS)

        # allowed value ranges
        allowed_value_range: Dict[str, Optional[str]] = {}
        allowed_value_range_el = state_variable_xml.find(
            "service:allowedValueRange", NS
        )
        if allowed_value_range_el is not None:
            allowed_value_range = {
                "min": allowed_value_range_el.findtext("service:minimum", None, NS),
                "max": allowed_value_range_el.findtext("service:maximum", None, NS),
                "step": allowed_value_range_el.findtext("service:step", None, NS),
            }

        # allowed value list
        allowed_values: Optional[List[str]] = None
        allowed_value_list_el = state_variable_xml.find("service:allowedValueList", NS)
        if allowed_value_list_el is not None:
            allowed_values = [
                v.text
                for v in allowed_value_list_el.findall("service:allowedValue", NS)
                if v.text is not None
            ]

        type_info = StateVariableTypeInfo(
            data_type=data_type,
            data_type_mapping=data_type_mapping,
            default_value=default_value,
            allowed_value_range=allowed_value_range,
            allowed_values=allowed_values,
            xml=state_variable_xml,
        )
        name = state_variable_xml.findtext("service:name", "", NS).strip()
        return StateVariableInfo(
            name=name,
            send_events=send_events,
            type_info=type_info,
            xml=state_variable_xml,
        )