# pylint: disable=no-value-for-parameter
import asyncio
import logging
from aiohap import Accessory, AccessoryServer, Category, HTTPTransport, services
from caspia.meadow.services import ServiceBase
from . import links
from .config import Config
logger = logging.getLogger(__name__)
[docs]class BridgeAccessory(Accessory):
category = Category.BRIDGE
info = services.AccessoryInformation
[docs]class Bridge:
def __init__(self, name, config: Config, storage, loop=None):
self.name = name
self.config = config
self.loop = loop
self._storage = storage
self._find_bridge_config()
self._prepare_accessory_server()
self._needs_deploy = False
self._links = []
self._service_link = dict() # service.name: link
def _find_bridge_config(self):
for bridge_cfg in self.config.bridges:
if bridge_cfg.name == self.name:
self.bridge_config = bridge_cfg
break
else:
raise RuntimeError('Bridge %s does not have configuration.' % self.name)
def _prepare_accessory_server(self):
port = self.bridge_config.port()
self._transport = HTTPTransport(
self.config.host,
port=port,
loop=self.loop,
notification_coalesce_interval=self.config.notification_coalesce_interval,
notification_coalesce_delay=self.config.notification_coalesce_delay,
external_address=self.config.external_address)
self._accessory_server = AccessoryServer(self.name,
self._transport,
password=self.bridge_config.password,
storage=self._storage)
self._add_bridge_accessory()
def _add_bridge_accessory(self):
acc = BridgeAccessory(self.name)
acc.info.set(name=self.name, model='Caspia Bridge', manufacturer='Caspia')
self._accessory_server.add_accessory(acc)
[docs] def load_known_services(self, lookup):
known_services = self._storage.get('services')
if known_services is None or isinstance(known_services, dict):
return []
else:
loaded = []
for spec in known_services:
service_name, stype = spec['name'], spec['type']
instance = lookup[stype](service_name)
instance.load_definition(spec)
if self.add(instance):
loaded.append(instance)
return loaded
def _store_known_services(self):
known_services = set()
for link in self._links:
for service in link.dependant_services:
known_services.add(service)
self._storage['services'] = [s.serialize() for s in known_services]
@property
def accessory_server(self) -> AccessoryServer:
return self._accessory_server
@staticmethod
def _get_link_cls(service: ServiceBase):
for link_cls in links.base.Link.all():
if link_cls.meadow_service_type == service.type:
return link_cls
return None
[docs] @staticmethod
def is_bridgable(service: ServiceBase) -> bool:
return Bridge._get_link_cls(service) is not None
[docs] def add(self, service: ServiceBase) -> bool:
if not self.bridge_config.allows_service(service.name):
return False
if len(self.accessory_server.accessories) >= 95:
return False
link_cls = Bridge._get_link_cls(service)
if link_cls is None:
return False
link = link_cls(self, service)
self._links.append(link)
self._needs_deploy = True
for s in link.dependant_services:
self._service_link[s.name] = link
return link
[docs] def set_needs_update(self, service: ServiceBase):
link = self._service_link.get(service.name)
if link:
asyncio.ensure_future(link.update(), loop=self.loop)
[docs] async def deploy(self):
if not self._transport.running and len(self._accessory_server.accessories) > 1:
logger.info('Starting new bridge %s', self)
await self._accessory_server.start()
elif self._needs_deploy:
logger.info('Updating accessory server for %s', self)
await self._accessory_server.update()
self._needs_deploy = False
self._store_known_services()
if self._transport.running:
self._storage['|'.join(['bridge', self.name, 'port'])] = self._transport.port
def __repr__(self):
return '<Bridge %s at %x>' % (self.name, id(self))