You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

151 lines
4.8 KiB

# -*- coding: utf-8 -*-
"""UPnP base-profile module."""
import logging
from datetime import timedelta
from typing import Dict
from typing import List
from typing import Optional
from async_upnp_client import UpnpAction
from async_upnp_client import UpnpDevice
from async_upnp_client import UpnpEventHandler
from async_upnp_client import UpnpService
from async_upnp_client import UpnpStateVariable
from async_upnp_client.discovery import async_discover
_LOGGER = logging.getLogger(__name__)
SUBSCRIBE_TIMEOUT = timedelta(minutes=30)
class UpnpProfileDevice:
"""
Base class for UpnpProfileDevices.
Override _SERVICE_TYPES for aliases.
"""
DEVICE_TYPES = []
_SERVICE_TYPES = {}
@classmethod
async def async_discover(cls) -> Dict:
"""
Discovery this device type.
This only return discovery info, not a profile itself.
:return:
"""
return [
device
for device in await async_discover()
if device['st'] in cls.DEVICE_TYPES
]
def __init__(self,
device: UpnpDevice,
event_handler: UpnpEventHandler) -> None:
"""Initializer."""
self._device = device
self._event_handler = event_handler
self.on_event = None
@property
def name(self) -> str:
"""Get the name of the device."""
return self._device.name
@property
def udn(self) -> str:
"""Get the UDN of the device."""
return self._device.udn
def _service(self, service_type_abbreviation: str) -> Optional[UpnpService]:
"""Get UpnpService by service_type or alias."""
if not self._device:
return None
if service_type_abbreviation not in self._SERVICE_TYPES:
return None
for service_type in self._SERVICE_TYPES[service_type_abbreviation]:
service = self._device.service(service_type)
if service:
return service
return None
def _state_variable(self, service_name: str,
state_variable_name: str) -> Optional[UpnpStateVariable]:
"""Get state_variable from service."""
service = self._service(service_name)
if not service:
return None
return service.state_variable(state_variable_name)
def _action(self, service_name: str, action_name: str) -> Optional[UpnpAction]:
"""Check if service has action."""
service = self._service(service_name)
if not service:
return None
return service.action(action_name)
def _interesting_service(self, service: UpnpService) -> bool:
"""Check if service is a service we're interested in."""
service_type = service.service_type
for service_types in self._SERVICE_TYPES.values():
if service_type in service_types:
return True
return False
async def async_subscribe_services(self) -> timedelta:
"""(Re-)Subscribe to services."""
for service in self._device.services.values():
# ensure we are interested in this service_type
if not self._interesting_service(service):
continue
service.on_event = self._on_event
if self._event_handler.sid_for_service(service) is None:
_LOGGER.debug('Subscribing to service: %s', service)
success, _ = \
await self._event_handler.async_subscribe(service, timeout=SUBSCRIBE_TIMEOUT)
if not success:
_LOGGER.debug('Failed subscribing to: %s', service)
else:
_LOGGER.debug('Resubscribing to service: %s', service)
success, _ = \
await self._event_handler.async_resubscribe(service, timeout=SUBSCRIBE_TIMEOUT)
# could not renew subscription, try subscribing again
if not success:
_LOGGER.debug('Failed resubscribing to: %s', service)
success, _ = \
await self._event_handler.async_subscribe(service,
timeout=SUBSCRIBE_TIMEOUT)
if not success:
_LOGGER.debug('Failed subscribing to: %s', service)
return SUBSCRIBE_TIMEOUT
async def async_unsubscribe_services(self):
"""Unsubscribe from all subscribed services."""
await self._event_handler.async_unsubscribe_all()
def _on_event(self, service: UpnpService, state_variables: List[UpnpStateVariable]):
"""
State variable(s) changed. Override to handle events.
:param service Service which sent the event.
:param state_variables State variables which have been changed.
"""