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
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)
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
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
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
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, )