152 lines
4.8 KiB
Python
152 lines
4.8 KiB
Python
|
# -*- 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.
|
||
|
"""
|