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

4 years ago
  1. # -*- coding: utf-8 -*-
  2. """UPnP base-profile module."""
  3. import logging
  4. from datetime import timedelta
  5. from typing import Dict
  6. from typing import List
  7. from typing import Optional
  8. from async_upnp_client import UpnpAction
  9. from async_upnp_client import UpnpDevice
  10. from async_upnp_client import UpnpEventHandler
  11. from async_upnp_client import UpnpService
  12. from async_upnp_client import UpnpStateVariable
  13. from async_upnp_client.discovery import async_discover
  14. _LOGGER = logging.getLogger(__name__)
  15. SUBSCRIBE_TIMEOUT = timedelta(minutes=30)
  16. class UpnpProfileDevice:
  17. """
  18. Base class for UpnpProfileDevices.
  19. Override _SERVICE_TYPES for aliases.
  20. """
  21. DEVICE_TYPES = []
  22. _SERVICE_TYPES = {}
  23. @classmethod
  24. async def async_discover(cls) -> Dict:
  25. """
  26. Discovery this device type.
  27. This only return discovery info, not a profile itself.
  28. :return:
  29. """
  30. return [
  31. device
  32. for device in await async_discover()
  33. if device['st'] in cls.DEVICE_TYPES
  34. ]
  35. def __init__(self,
  36. device: UpnpDevice,
  37. event_handler: UpnpEventHandler) -> None:
  38. """Initializer."""
  39. self._device = device
  40. self._event_handler = event_handler
  41. self.on_event = None
  42. @property
  43. def name(self) -> str:
  44. """Get the name of the device."""
  45. return self._device.name
  46. @property
  47. def udn(self) -> str:
  48. """Get the UDN of the device."""
  49. return self._device.udn
  50. def _service(self, service_type_abbreviation: str) -> Optional[UpnpService]:
  51. """Get UpnpService by service_type or alias."""
  52. if not self._device:
  53. return None
  54. if service_type_abbreviation not in self._SERVICE_TYPES:
  55. return None
  56. for service_type in self._SERVICE_TYPES[service_type_abbreviation]:
  57. service = self._device.service(service_type)
  58. if service:
  59. return service
  60. return None
  61. def _state_variable(self, service_name: str,
  62. state_variable_name: str) -> Optional[UpnpStateVariable]:
  63. """Get state_variable from service."""
  64. service = self._service(service_name)
  65. if not service:
  66. return None
  67. return service.state_variable(state_variable_name)
  68. def _action(self, service_name: str, action_name: str) -> Optional[UpnpAction]:
  69. """Check if service has action."""
  70. service = self._service(service_name)
  71. if not service:
  72. return None
  73. return service.action(action_name)
  74. def _interesting_service(self, service: UpnpService) -> bool:
  75. """Check if service is a service we're interested in."""
  76. service_type = service.service_type
  77. for service_types in self._SERVICE_TYPES.values():
  78. if service_type in service_types:
  79. return True
  80. return False
  81. async def async_subscribe_services(self) -> timedelta:
  82. """(Re-)Subscribe to services."""
  83. for service in self._device.services.values():
  84. # ensure we are interested in this service_type
  85. if not self._interesting_service(service):
  86. continue
  87. service.on_event = self._on_event
  88. if self._event_handler.sid_for_service(service) is None:
  89. _LOGGER.debug('Subscribing to service: %s', service)
  90. success, _ = \
  91. await self._event_handler.async_subscribe(service, timeout=SUBSCRIBE_TIMEOUT)
  92. if not success:
  93. _LOGGER.debug('Failed subscribing to: %s', service)
  94. else:
  95. _LOGGER.debug('Resubscribing to service: %s', service)
  96. success, _ = \
  97. await self._event_handler.async_resubscribe(service, timeout=SUBSCRIBE_TIMEOUT)
  98. # could not renew subscription, try subscribing again
  99. if not success:
  100. _LOGGER.debug('Failed resubscribing to: %s', service)
  101. success, _ = \
  102. await self._event_handler.async_subscribe(service,
  103. timeout=SUBSCRIBE_TIMEOUT)
  104. if not success:
  105. _LOGGER.debug('Failed subscribing to: %s', service)
  106. return SUBSCRIBE_TIMEOUT
  107. async def async_unsubscribe_services(self):
  108. """Unsubscribe from all subscribed services."""
  109. await self._event_handler.async_unsubscribe_all()
  110. def _on_event(self, service: UpnpService, state_variables: List[UpnpStateVariable]):
  111. """
  112. State variable(s) changed. Override to handle events.
  113. :param service Service which sent the event.
  114. :param state_variables State variables which have been changed.
  115. """