Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
87 changes: 38 additions & 49 deletions homeassistant/components/homekit/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,72 +77,61 @@ def get_accessory(hass, state, aid, config):
_LOGGER.warning('The entitiy "%s" is not supported, since it '
'generates an invalid aid, please change it.',
state.entity_id)
return None
return

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Although they are the same, I would suggest we keep return None where we care / intend to use the return of a function

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have again added it to get_accessory and generate_aid. In cases where it is an abort condition (like HomeKit.start(), HomeKit.stop(), HomeAccessory.update_state_callback()) I would prefer to stay with just return.


if state.domain == 'sensor':
unit = state.attributes.get(ATTR_UNIT_OF_MEASUREMENT)
if unit == TEMP_CELSIUS or unit == TEMP_FAHRENHEIT:
_LOGGER.debug('Add "%s" as "%s"',
state.entity_id, 'TemperatureSensor')
return TYPES['TemperatureSensor'](hass, state.entity_id,
state.name, aid=aid)
elif unit == '%':
_LOGGER.debug('Add "%s" as %s"',
state.entity_id, 'HumiditySensor')
return TYPES['HumiditySensor'](hass, state.entity_id, state.name,
aid=aid)

elif state.domain == 'binary_sensor' or state.domain == 'device_tracker':
_LOGGER.debug('Add "%s" as "%s"', state.entity_id, 'BinarySensor')
return TYPES['BinarySensor'](hass, state.entity_id,
state.name, aid=aid)
a_type = None
a_kwargs = {'aid': aid}

elif state.domain == 'cover':
# Only add covers that support set_cover_position
features = state.attributes.get(ATTR_SUPPORTED_FEATURES, 0)
if features & SUPPORT_SET_POSITION:
_LOGGER.debug('Add "%s" as "%s"',
state.entity_id, 'WindowCovering')
return TYPES['WindowCovering'](hass, state.entity_id, state.name,
aid=aid)
if state.domain == 'alarm_control_panel':

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Insert a_type = None

a_type = 'SecuritySystem'
a_kwargs['alarm_code'] = config.get(ATTR_CODE)

elif state.domain == 'alarm_control_panel':
_LOGGER.debug('Add "%s" as "%s"', state.entity_id, 'SecuritySystem')
return TYPES['SecuritySystem'](hass, state.entity_id, state.name,
alarm_code=config.get(ATTR_CODE),
aid=aid)
elif state.domain == 'binary_sensor' or state.domain == 'device_tracker':
a_type = 'BinarySensor'

elif state.domain == 'climate':
features = state.attributes.get(ATTR_SUPPORTED_FEATURES, 0)
support_temp_range = SUPPORT_TARGET_TEMPERATURE_LOW | \
SUPPORT_TARGET_TEMPERATURE_HIGH
# Check if climate device supports auto mode
support_auto = bool(features & support_temp_range)
a_type = 'Thermostat'
a_kwargs['support_auto'] = bool(features & support_temp_range)

_LOGGER.debug('Add "%s" as "%s"', state.entity_id, 'Thermostat')
return TYPES['Thermostat'](hass, state.entity_id,
state.name, support_auto, aid=aid)
elif state.domain == 'cover':
# Only add covers that support set_cover_position
features = state.attributes.get(ATTR_SUPPORTED_FEATURES, 0)
if features & SUPPORT_SET_POSITION:
a_type = 'WindowCovering'

elif state.domain == 'light':
_LOGGER.debug('Add "%s" as "%s"', state.entity_id, 'Light')
return TYPES['Light'](hass, state.entity_id, state.name, aid=aid)
a_type = 'Light'

elif state.domain == 'lock':
return TYPES['Lock'](hass, state.entity_id, state.name, aid=aid)
a_type = 'Lock'

elif state.domain == 'sensor':
unit = state.attributes.get(ATTR_UNIT_OF_MEASUREMENT)
if unit == TEMP_CELSIUS or unit == TEMP_FAHRENHEIT:
a_type = 'TemperatureSensor'
elif unit == '%':
a_type = 'HumiditySensor'

elif state.domain == 'switch' or state.domain == 'remote' \
or state.domain == 'input_boolean' or state.domain == 'script':
_LOGGER.debug('Add "%s" as "%s"', state.entity_id, 'Switch')
return TYPES['Switch'](hass, state.entity_id, state.name, aid=aid)
a_type = 'Switch'

if a_type is None:
return

return None
_LOGGER.debug('Add "%s" as "%s"', state.entity_id, a_type)
return TYPES[a_type](hass, state.name, state.entity_id, **a_kwargs)


def generate_aid(entity_id):
"""Generate accessory aid with zlib adler32."""
aid = adler32(entity_id.encode('utf-8'))
if aid == 0 or aid == 1:
return None
return
return aid


Expand All @@ -151,7 +140,7 @@ class HomeKit():

def __init__(self, hass, port, entity_filter, entity_config):
"""Initialize a HomeKit object."""
self._hass = hass
self.hass = hass
self._port = port
self._filter = entity_filter
self._config = entity_config
Expand All @@ -164,11 +153,11 @@ def setup(self):
"""Setup bridge and accessory driver."""
from .accessories import HomeBridge, HomeDriver

self._hass.bus.async_listen_once(
self.hass.bus.async_listen_once(
EVENT_HOMEASSISTANT_STOP, self.stop)

path = self._hass.config.path(HOMEKIT_FILE)
self.bridge = HomeBridge(self._hass)
path = self.hass.config.path(HOMEKIT_FILE)
self.bridge = HomeBridge(self.hass)
self.driver = HomeDriver(self.bridge, self._port, get_local_ip(), path)

def add_bridge_accessory(self, state):
Expand All @@ -177,7 +166,7 @@ def add_bridge_accessory(self, state):
return
aid = generate_aid(state.entity_id)
conf = self._config.pop(state.entity_id, {})
acc = get_accessory(self._hass, state, aid, conf)
acc = get_accessory(self.hass, state, aid, conf)
if acc is not None:
self.bridge.add_accessory(acc)

Expand All @@ -192,12 +181,12 @@ def start(self, *args):
type_covers, type_lights, type_locks, type_security_systems,
type_sensors, type_switches, type_thermostats)

for state in self._hass.states.all():
for state in self.hass.states.all():
self.add_bridge_accessory(state)
self.bridge.set_broker(self.driver)

if not self.bridge.paired:
show_setup_message(self.bridge, self._hass)
show_setup_message(self.hass, self.bridge)

_LOGGER.debug('Driver start')
self.driver.start()
Expand Down
39 changes: 26 additions & 13 deletions homeassistant/components/homekit/accessories.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@
from homeassistant.util import dt as dt_util

from .const import (
DEBOUNCE_TIMEOUT, ACCESSORY_MODEL, ACCESSORY_NAME, BRIDGE_MODEL,
BRIDGE_NAME, MANUFACTURER, SERV_ACCESSORY_INFO, CHAR_MANUFACTURER,
DEBOUNCE_TIMEOUT, BRIDGE_MODEL, BRIDGE_NAME, MANUFACTURER,
SERV_ACCESSORY_INFO, CHAR_MANUFACTURER,
CHAR_MODEL, CHAR_NAME, CHAR_SERIAL_NUMBER)
from .util import (
show_setup_message, dismiss_setup_message)
Expand Down Expand Up @@ -85,34 +85,47 @@ def set_accessory_info(acc, name, model, manufacturer=MANUFACTURER,
class HomeAccessory(Accessory):
"""Adapter class for Accessory."""

# pylint: disable=no-member

def __init__(self, name=ACCESSORY_NAME, model=ACCESSORY_MODEL,
category='OTHER', **kwargs):
def __init__(self, hass, name, entity_id, category='OTHER', **kwargs):
"""Initialize a Accessory object."""
super().__init__(name, **kwargs)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this **kwargs or the combination of config and aid that should be passed here?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will change **kwargs to aid.

set_accessory_info(self, name, model)
set_accessory_info(self, name, model=entity_id)
self.category = getattr(Category, category, Category.OTHER)
self.entity_id = entity_id
self.hass = hass

def _set_services(self):
add_preload_service(self, SERV_ACCESSORY_INFO)

def run(self):
"""Method called by accessory after driver is started."""
state = self.hass.states.get(self.entity_id)
self.update_state(new_state=state)
self.update_state_callback(new_state=state)
async_track_state_change(
self.hass, self.entity_id, self.update_state)
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):

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 This is a good idea and seem to simplify things as well

"""Method called on state change to update HomeKit value.

Overridden by accessory types.
"""
pass


class HomeBridge(Bridge):
"""Adapter class for Bridge."""

def __init__(self, hass, name=BRIDGE_NAME,
model=BRIDGE_MODEL, **kwargs):
def __init__(self, hass, name=BRIDGE_NAME, **kwargs):
"""Initialize a Bridge object."""
super().__init__(name, **kwargs)
set_accessory_info(self, name, model)
set_accessory_info(self, name, model=BRIDGE_MODEL)
self.hass = hass

def _set_services(self):
Expand All @@ -130,7 +143,7 @@ def add_paired_client(self, client_uuid, client_public):
def remove_paired_client(self, client_uuid):
"""Override super function to show setup message if unpaired."""
super().remove_paired_client(client_uuid)
show_setup_message(self, self.hass)
show_setup_message(self.hass, self)


class HomeDriver(AccessoryDriver):
Expand Down
2 changes: 0 additions & 2 deletions homeassistant/components/homekit/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,6 @@
SERVICE_HOMEKIT_START = 'start'

# #### STRING CONSTANTS ####
ACCESSORY_MODEL = 'homekit.accessory'
ACCESSORY_NAME = 'Home Accessory'
BRIDGE_MODEL = 'homekit.bridge'
BRIDGE_NAME = 'Home Assistant'
MANUFACTURER = 'HomeAssistant'
Expand Down
15 changes: 4 additions & 11 deletions homeassistant/components/homekit/type_covers.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
CATEGORY_WINDOW_COVERING, SERV_WINDOW_COVERING,
CHAR_CURRENT_POSITION, CHAR_TARGET_POSITION, CHAR_POSITION_STATE)


_LOGGER = logging.getLogger(__name__)


Expand All @@ -20,13 +19,10 @@ class WindowCovering(HomeAccessory):
The cover entity must support: set_cover_position.
"""

def __init__(self, hass, entity_id, display_name, **kwargs):
def __init__(self, hass, name, entity_id, **kwargs):
"""Initialize a WindowCovering accessory object."""
super().__init__(display_name, entity_id,
CATEGORY_WINDOW_COVERING, **kwargs)

self.hass = hass
self.entity_id = entity_id
super().__init__(hass, name, entity_id,
category=CATEGORY_WINDOW_COVERING, **kwargs)

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Personally I would really like to move these calls to the HomeAccessory class somehow. Any ideas who to improve that?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On solution could be to use a decorator function for for the init method which calls super().__init__(). That would look like this:

def super_init(category):
    def category_wrapper(func_init):
        def wrapper(self, hass, name, entity_id, **kwargs):
            super(self.__class__, self).__init__(
                hass, name, entity_id, category=CATEGORY_LIGHT, **kwargs)
            func_init(self)
        return wrapper
    return category_wrapper


@TYPES.register('Light')
class Light(HomeAccessory):

    @super_init(CATEGORY_LIGHT)
    def __init__(self):

Should we do that or stay at the current:

    def __init__(self, hass, name, entity_id, **kwargs):
        super().__init__(hass, name, entity_id, category=CATEGORY_LIGHT, **kwargs)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't like the decorator and not sure how that will interfere with __init__

You could do this (note: category= at the end so they look the same at first glance)

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs, category=CATEGORY_LIGHT)

but being explicit might give you some code editor hints, so I like this version more:

    def __init__(self, hass, name, entity_id, **kwargs):
        super().__init__(hass, name, entity_id, **kwargs,
                         category=CATEGORY_LIGHT)

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The idea behind the decorator was to limit code redundancy between different types. You're right the it might be more difficult for editors to read, however I don't think that is that big of a deal since those attributes are already assigned in accessories.py/HomeAccessory and most inexperience developers will only copy existing types without fully understanding them.

Regarding the interference with __init__, it should change anything. The call order is as follows:

  1. HA start
  2. Module is loaded and __init__ call is replaced by decorater
  3. Call to light.__init__
  4. super().__init__()
  5. __init__()

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you dont like the super rather add a bootstrap/postinit method in the child classes that you call from the parent’s init. Type could be a class var on the child

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In HomeAccessory you then have:

    def __init__(self, hass, name, entity_id, **kwargs):
        """Initialize a Accessory object."""
        super().__init__(name, **kwargs)
        set_accessory_info(self, name, model=entity_id)
        self.entity_id = entity_id
        self.hass = hass
        cat = self.init_category()
        self.category = getattr(Category, cat, Category.OTHER)

with only def init_category(self) and no more __init__ in the rest, init_category then returns the category.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Extending on your idea what do you think about the following:

  • Making the category a static var of the type
  • Changing the __init__() of the types to something like init_setup() which is called from HomeAccessory.__init__()

@cdce8p cdce8p Apr 10, 2018

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

init_setup() might not be the best name. If you have something else in mind, I'm happy to change it.
The only caveat with this solution is, that I had to add # pylint: disable=attribute-defined-outside-init to every type file.


self.current_position = None
self.homekit_target = None
Expand Down Expand Up @@ -56,11 +52,8 @@ def move_cover(self, value):
self.hass.components.cover.set_cover_position(
value, self.entity_id)

def update_state(self, entity_id=None, old_state=None, new_state=None):
def update_state(self, new_state):
"""Update cover position after state changed."""
if new_state is None:
return

current_position = new_state.attributes.get(ATTR_CURRENT_POSITION)
if isinstance(current_position, int):
self.current_position = current_position
Expand Down
12 changes: 4 additions & 8 deletions homeassistant/components/homekit/type_lights.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,11 @@ class Light(HomeAccessory):
Currently supports: state, brightness, color temperature, rgb_color.
"""

def __init__(self, hass, entity_id, name, **kwargs):
def __init__(self, hass, name, entity_id, **kwargs):
"""Initialize a new Light accessory object."""
super().__init__(name, entity_id, CATEGORY_LIGHT, **kwargs)
super().__init__(hass, name, entity_id,
category=CATEGORY_LIGHT, **kwargs)

self.hass = hass
self.entity_id = entity_id
self._flag = {CHAR_ON: False, CHAR_BRIGHTNESS: False,
CHAR_HUE: False, CHAR_SATURATION: False,
CHAR_COLOR_TEMPERATURE: False, RGB_COLOR: False}
Expand Down Expand Up @@ -136,11 +135,8 @@ def set_color(self):
self.hass.components.light.turn_on(
self.entity_id, hs_color=color)

def update_state(self, entity_id=None, old_state=None, new_state=None):
def update_state(self, new_state):
"""Update light after state change."""
if not new_state:
return

# Handle State
state = new_state.state
if state in (STATE_ON, STATE_OFF):
Expand Down
13 changes: 4 additions & 9 deletions homeassistant/components/homekit/type_locks.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,10 @@ class Lock(HomeAccessory):
The lock entity must support: unlock and lock.
"""

def __init__(self, hass, entity_id, name, **kwargs):
def __init__(self, hass, name, entity_id, **kwargs):
"""Initialize a Lock accessory object."""
super().__init__(name, entity_id, CATEGORY_LOCK, **kwargs)

self.hass = hass
self.entity_id = entity_id
super().__init__(hass, name, entity_id,
category=CATEGORY_LOCK, **kwargs)

self.flag_target_state = False

Expand All @@ -58,11 +56,8 @@ def set_state(self, value):
params = {ATTR_ENTITY_ID: self.entity_id}
self.hass.services.call('lock', service, params)

def update_state(self, entity_id=None, old_state=None, new_state=None):
def update_state(self, new_state):
"""Update lock after state changed."""
if new_state is None:
return

hass_state = new_state.state
if hass_state in HASS_TO_HOMEKIT:
current_lock_state = HASS_TO_HOMEKIT[hass_state]
Expand Down
14 changes: 4 additions & 10 deletions homeassistant/components/homekit/type_security_systems.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,15 +27,12 @@
class SecuritySystem(HomeAccessory):
"""Generate an SecuritySystem accessory for an alarm control panel."""

def __init__(self, hass, entity_id, display_name, alarm_code, **kwargs):
def __init__(self, hass, name, entity_id, alarm_code, **kwargs):
"""Initialize a SecuritySystem accessory object."""
super().__init__(display_name, entity_id,
CATEGORY_ALARM_SYSTEM, **kwargs)
super().__init__(hass, name, entity_id,
category=CATEGORY_ALARM_SYSTEM, **kwargs)

self.hass = hass
self.entity_id = entity_id
self._alarm_code = alarm_code

self.flag_target_state = False

serv_alarm = add_preload_service(self, SERV_SECURITY_SYSTEM)
Expand All @@ -61,11 +58,8 @@ def set_security_state(self, value):
params[ATTR_CODE] = self._alarm_code
self.hass.services.call('alarm_control_panel', service, params)

def update_state(self, entity_id=None, old_state=None, new_state=None):
def update_state(self, new_state):
"""Update security state after state changed."""
if new_state is None:
return

hass_state = new_state.state
if hass_state in HASS_TO_HOMEKIT:
current_security_state = HASS_TO_HOMEKIT[hass_state]
Expand Down
Loading