Source code for caspia.hapbridge.bridge

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