2018-02-19 22:46:22 +00:00
|
|
|
"""Extend the basic Accessory and Bridge functions."""
|
2018-04-06 21:11:53 +00:00
|
|
|
from datetime import timedelta
|
|
|
|
from functools import wraps
|
|
|
|
from inspect import getmodule
|
2018-02-26 03:27:40 +00:00
|
|
|
import logging
|
|
|
|
|
2018-02-19 22:46:22 +00:00
|
|
|
from pyhap.accessory import Accessory, Bridge, Category
|
2018-03-15 01:48:21 +00:00
|
|
|
from pyhap.accessory_driver import AccessoryDriver
|
2018-02-19 22:46:22 +00:00
|
|
|
|
2018-04-11 20:24:14 +00:00
|
|
|
from homeassistant.core import callback as ha_callback
|
2018-04-06 21:11:53 +00:00
|
|
|
from homeassistant.helpers.event import (
|
|
|
|
async_track_state_change, track_point_in_utc_time)
|
|
|
|
from homeassistant.util import dt as dt_util
|
2018-03-16 00:05:28 +00:00
|
|
|
|
2018-02-19 22:46:22 +00:00
|
|
|
from .const import (
|
2018-04-11 20:24:14 +00:00
|
|
|
DEBOUNCE_TIMEOUT, BRIDGE_MODEL, BRIDGE_NAME, MANUFACTURER,
|
|
|
|
SERV_ACCESSORY_INFO, CHAR_MANUFACTURER,
|
2018-04-06 21:11:53 +00:00
|
|
|
CHAR_MODEL, CHAR_NAME, CHAR_SERIAL_NUMBER)
|
2018-03-15 01:48:21 +00:00
|
|
|
from .util import (
|
|
|
|
show_setup_message, dismiss_setup_message)
|
2018-02-19 22:46:22 +00:00
|
|
|
|
2018-02-26 03:27:40 +00:00
|
|
|
_LOGGER = logging.getLogger(__name__)
|
2018-02-19 22:46:22 +00:00
|
|
|
|
|
|
|
|
2018-04-06 21:11:53 +00:00
|
|
|
def debounce(func):
|
|
|
|
"""Decorator function. Debounce callbacks form HomeKit."""
|
2018-04-11 20:24:14 +00:00
|
|
|
@ha_callback
|
2018-04-06 21:11:53 +00:00
|
|
|
def call_later_listener(*args):
|
|
|
|
"""Callback listener called from call_later."""
|
|
|
|
# pylint: disable=unsubscriptable-object
|
|
|
|
nonlocal lastargs, remove_listener
|
|
|
|
hass = lastargs['hass']
|
|
|
|
hass.async_add_job(func, *lastargs['args'])
|
|
|
|
lastargs = remove_listener = None
|
|
|
|
|
|
|
|
@wraps(func)
|
|
|
|
def wrapper(*args):
|
|
|
|
"""Wrapper starts async timer.
|
|
|
|
|
|
|
|
The accessory must have 'self.hass' and 'self.entity_id' as attributes.
|
|
|
|
"""
|
|
|
|
# pylint: disable=not-callable
|
|
|
|
hass = args[0].hass
|
|
|
|
nonlocal lastargs, remove_listener
|
|
|
|
if remove_listener:
|
|
|
|
remove_listener()
|
|
|
|
lastargs = remove_listener = None
|
|
|
|
lastargs = {'hass': hass, 'args': [*args]}
|
|
|
|
remove_listener = track_point_in_utc_time(
|
|
|
|
hass, call_later_listener,
|
|
|
|
dt_util.utcnow() + timedelta(seconds=DEBOUNCE_TIMEOUT))
|
|
|
|
logger.debug('%s: Start %s timeout', args[0].entity_id,
|
|
|
|
func.__name__.replace('set_', ''))
|
|
|
|
|
|
|
|
remove_listener = None
|
|
|
|
lastargs = None
|
|
|
|
name = getmodule(func).__name__
|
|
|
|
logger = logging.getLogger(name)
|
|
|
|
return wrapper
|
|
|
|
|
|
|
|
|
2018-03-15 01:48:21 +00:00
|
|
|
def add_preload_service(acc, service, chars=None):
|
2018-02-26 03:27:40 +00:00
|
|
|
"""Define and return a service to be available for the accessory."""
|
|
|
|
from pyhap.loader import get_serv_loader, get_char_loader
|
|
|
|
service = get_serv_loader().get(service)
|
|
|
|
if chars:
|
|
|
|
chars = chars if isinstance(chars, list) else [chars]
|
|
|
|
for char_name in chars:
|
|
|
|
char = get_char_loader().get(char_name)
|
|
|
|
service.add_characteristic(char)
|
|
|
|
acc.add_service(service)
|
|
|
|
return service
|
2018-02-19 22:46:22 +00:00
|
|
|
|
|
|
|
|
2018-04-11 20:24:14 +00:00
|
|
|
def setup_char(char_name, service, value=None, properties=None, callback=None):
|
|
|
|
"""Helper function to return fully configured characteristic."""
|
|
|
|
char = service.get_characteristic(char_name)
|
|
|
|
if value:
|
|
|
|
char.value = value
|
|
|
|
if properties:
|
|
|
|
char.override_properties(properties)
|
|
|
|
if callback:
|
|
|
|
char.setter_callback = callback
|
|
|
|
return char
|
|
|
|
|
|
|
|
|
2018-03-15 01:48:21 +00:00
|
|
|
def set_accessory_info(acc, name, model, manufacturer=MANUFACTURER,
|
|
|
|
serial_number='0000'):
|
|
|
|
"""Set the default accessory information."""
|
|
|
|
service = acc.get_service(SERV_ACCESSORY_INFO)
|
|
|
|
service.get_characteristic(CHAR_NAME).set_value(name)
|
|
|
|
service.get_characteristic(CHAR_MODEL).set_value(model)
|
|
|
|
service.get_characteristic(CHAR_MANUFACTURER).set_value(manufacturer)
|
|
|
|
service.get_characteristic(CHAR_SERIAL_NUMBER).set_value(serial_number)
|
|
|
|
|
|
|
|
|
2018-02-26 03:27:40 +00:00
|
|
|
class HomeAccessory(Accessory):
|
2018-03-15 01:48:21 +00:00
|
|
|
"""Adapter class for Accessory."""
|
2018-02-26 03:27:40 +00:00
|
|
|
|
2018-04-11 20:24:14 +00:00
|
|
|
def __init__(self, hass, name, entity_id, aid, category):
|
2018-02-26 03:27:40 +00:00
|
|
|
"""Initialize a Accessory object."""
|
2018-04-11 20:24:14 +00:00
|
|
|
super().__init__(name, aid=aid)
|
|
|
|
set_accessory_info(self, name, model=entity_id)
|
2018-02-26 03:27:40 +00:00
|
|
|
self.category = getattr(Category, category, Category.OTHER)
|
2018-04-11 20:24:14 +00:00
|
|
|
self.entity_id = entity_id
|
|
|
|
self.hass = hass
|
2018-02-19 22:46:22 +00:00
|
|
|
|
2018-03-01 23:20:02 +00:00
|
|
|
def _set_services(self):
|
|
|
|
add_preload_service(self, SERV_ACCESSORY_INFO)
|
|
|
|
|
2018-03-16 00:05:28 +00:00
|
|
|
def run(self):
|
|
|
|
"""Method called by accessory after driver is started."""
|
2018-04-04 22:52:25 +00:00
|
|
|
state = self.hass.states.get(self.entity_id)
|
2018-04-11 20:24:14 +00:00
|
|
|
self.update_state_callback(new_state=state)
|
2018-03-16 00:05:28 +00:00
|
|
|
async_track_state_change(
|
2018-04-11 20:24:14 +00:00
|
|
|
self.hass, self.entity_id, self.update_state_callback)
|
|
|
|
|
|
|
|
def update_state_callback(self, entity_id=None, old_state=None,
|
|
|
|
new_state=None):
|
|
|
|
"""Callback from state change listener."""
|
|
|
|
_LOGGER.debug('New_state: %s', new_state)
|
|
|
|
if new_state is None:
|
|
|
|
return
|
|
|
|
self.update_state(new_state)
|
|
|
|
|
|
|
|
def update_state(self, new_state):
|
|
|
|
"""Method called on state change to update HomeKit value.
|
|
|
|
|
|
|
|
Overridden by accessory types.
|
|
|
|
"""
|
|
|
|
pass
|
2018-03-16 00:05:28 +00:00
|
|
|
|
2018-02-19 22:46:22 +00:00
|
|
|
|
|
|
|
class HomeBridge(Bridge):
|
2018-03-15 01:48:21 +00:00
|
|
|
"""Adapter class for Bridge."""
|
2018-02-19 22:46:22 +00:00
|
|
|
|
2018-04-11 20:24:14 +00:00
|
|
|
def __init__(self, hass, name=BRIDGE_NAME):
|
2018-02-19 22:46:22 +00:00
|
|
|
"""Initialize a Bridge object."""
|
2018-04-11 20:24:14 +00:00
|
|
|
super().__init__(name)
|
|
|
|
set_accessory_info(self, name, model=BRIDGE_MODEL)
|
2018-04-04 22:52:25 +00:00
|
|
|
self.hass = hass
|
2018-03-01 23:20:02 +00:00
|
|
|
|
|
|
|
def _set_services(self):
|
|
|
|
add_preload_service(self, SERV_ACCESSORY_INFO)
|
2018-03-15 01:48:21 +00:00
|
|
|
|
|
|
|
def setup_message(self):
|
|
|
|
"""Prevent print of pyhap setup message to terminal."""
|
|
|
|
pass
|
|
|
|
|
|
|
|
def add_paired_client(self, client_uuid, client_public):
|
|
|
|
"""Override super function to dismiss setup message if paired."""
|
|
|
|
super().add_paired_client(client_uuid, client_public)
|
2018-04-04 22:52:25 +00:00
|
|
|
dismiss_setup_message(self.hass)
|
2018-03-15 01:48:21 +00:00
|
|
|
|
|
|
|
def remove_paired_client(self, client_uuid):
|
|
|
|
"""Override super function to show setup message if unpaired."""
|
|
|
|
super().remove_paired_client(client_uuid)
|
2018-04-11 20:24:14 +00:00
|
|
|
show_setup_message(self.hass, self)
|
2018-03-15 01:48:21 +00:00
|
|
|
|
|
|
|
|
|
|
|
class HomeDriver(AccessoryDriver):
|
|
|
|
"""Adapter class for AccessoryDriver."""
|
|
|
|
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
|
|
"""Initialize a AccessoryDriver object."""
|
|
|
|
super().__init__(*args, **kwargs)
|