New Wink services. pair new device, rename, and delete, add new lock key code. Add water heater support (#9303)

* Pair new device, rename, delete, and lock key code services. Also add water heater support.

* Fixed tox
This commit is contained in:
William Scanlon 2017-09-27 02:17:55 -04:00 committed by Paulus Schoutsen
parent 9d839f1f53
commit 312de6b3a3
9 changed files with 363 additions and 124 deletions

View File

@ -136,8 +136,9 @@ class WinkHub(WinkBinarySensorDevice):
def device_state_attributes(self): def device_state_attributes(self):
"""Return the state attributes.""" """Return the state attributes."""
return { return {
'update needed': self.wink.update_needed(), 'update_needed': self.wink.update_needed(),
'firmware version': self.wink.firmware_version() 'firmware_version': self.wink.firmware_version(),
'pairing_mode': self.wink.pairing_mode()
} }

View File

@ -44,6 +44,12 @@ STATE_IDLE = 'idle'
STATE_AUTO = 'auto' STATE_AUTO = 'auto'
STATE_DRY = 'dry' STATE_DRY = 'dry'
STATE_FAN_ONLY = 'fan_only' STATE_FAN_ONLY = 'fan_only'
STATE_ECO = 'eco'
STATE_ELECTRIC = 'electric'
STATE_PERFORMANCE = 'performance'
STATE_HIGH_DEMAND = 'high_demand'
STATE_HEAT_PUMP = 'heat_pump'
STATE_GAS = 'gas'
ATTR_CURRENT_TEMPERATURE = 'current_temperature' ATTR_CURRENT_TEMPERATURE = 'current_temperature'
ATTR_MAX_TEMP = 'max_temp' ATTR_MAX_TEMP = 'max_temp'

View File

@ -114,7 +114,7 @@ class DemoClimate(ClimateDevice):
@property @property
def is_aux_heat_on(self): def is_aux_heat_on(self):
"""Return true if away mode is on.""" """Return true if aux heat is on."""
return self._aux return self._aux
@property @property
@ -183,7 +183,7 @@ class DemoClimate(ClimateDevice):
self.schedule_update_ha_state() self.schedule_update_ha_state()
def turn_aux_heat_on(self): def turn_aux_heat_on(self):
"""Turn away auxiliary heater on.""" """Turn auxillary heater on."""
self._aux = True self._aux = True
self.schedule_update_ha_state() self.schedule_update_ha_state()

View File

@ -1,30 +1,45 @@
""" """
Support for Wink thermostats. Support for Wink thermostats, Air Conditioners, and Water Heaters.
For more details about this platform, please refer to the documentation at For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/climate.wink/ https://home-assistant.io/components/climate.wink/
""" """
import logging
import asyncio import asyncio
from homeassistant.components.wink import WinkDevice, DOMAIN from homeassistant.components.wink import WinkDevice, DOMAIN
from homeassistant.components.climate import ( from homeassistant.components.climate import (
STATE_AUTO, STATE_COOL, STATE_HEAT, ClimateDevice, STATE_AUTO, STATE_COOL, STATE_HEAT, ClimateDevice,
ATTR_TARGET_TEMP_HIGH, ATTR_TARGET_TEMP_LOW, ATTR_TARGET_TEMP_HIGH, ATTR_TARGET_TEMP_LOW,
ATTR_TEMPERATURE, ATTR_TEMPERATURE, STATE_FAN_ONLY,
ATTR_CURRENT_HUMIDITY) ATTR_CURRENT_HUMIDITY, STATE_ECO, STATE_ELECTRIC,
STATE_PERFORMANCE, STATE_HIGH_DEMAND,
STATE_HEAT_PUMP, STATE_GAS)
from homeassistant.const import ( from homeassistant.const import (
TEMP_CELSIUS, STATE_ON, TEMP_CELSIUS, STATE_ON,
STATE_OFF, STATE_UNKNOWN) STATE_OFF, STATE_UNKNOWN)
_LOGGER = logging.getLogger(__name__)
DEPENDENCIES = ['wink'] DEPENDENCIES = ['wink']
STATE_AUX = 'aux'
STATE_ECO = 'eco'
STATE_FAN = 'fan'
SPEED_LOW = 'low' SPEED_LOW = 'low'
SPEED_MEDIUM = 'medium' SPEED_MEDIUM = 'medium'
SPEED_HIGH = 'high' SPEED_HIGH = 'high'
HA_STATE_TO_WINK = {STATE_AUTO: 'auto',
STATE_ECO: 'eco',
STATE_FAN_ONLY: 'fan_only',
STATE_HEAT: 'heat_only',
STATE_COOL: 'cool_only',
STATE_PERFORMANCE: 'performance',
STATE_HIGH_DEMAND: 'high_demand',
STATE_HEAT_PUMP: 'heat_pump',
STATE_ELECTRIC: 'electric_only',
STATE_GAS: 'gas',
STATE_OFF: 'off'}
WINK_STATE_TO_HA = {value: key for key, value in HA_STATE_TO_WINK.items()}
ATTR_EXTERNAL_TEMPERATURE = "external_temperature" ATTR_EXTERNAL_TEMPERATURE = "external_temperature"
ATTR_SMART_TEMPERATURE = "smart_temperature" ATTR_SMART_TEMPERATURE = "smart_temperature"
ATTR_ECO_TARGET = "eco_target" ATTR_ECO_TARGET = "eco_target"
@ -32,28 +47,26 @@ ATTR_OCCUPIED = "occupied"
def setup_platform(hass, config, add_devices, discovery_info=None): def setup_platform(hass, config, add_devices, discovery_info=None):
"""Set up the Wink thermostat.""" """Set up the Wink climate devices."""
import pywink import pywink
temp_unit = hass.config.units.temperature_unit
for climate in pywink.get_thermostats(): for climate in pywink.get_thermostats():
_id = climate.object_id() + climate.name() _id = climate.object_id() + climate.name()
if _id not in hass.data[DOMAIN]['unique_ids']: if _id not in hass.data[DOMAIN]['unique_ids']:
add_devices([WinkThermostat(climate, hass, temp_unit)]) add_devices([WinkThermostat(climate, hass)])
for climate in pywink.get_air_conditioners(): for climate in pywink.get_air_conditioners():
_id = climate.object_id() + climate.name() _id = climate.object_id() + climate.name()
if _id not in hass.data[DOMAIN]['unique_ids']: if _id not in hass.data[DOMAIN]['unique_ids']:
add_devices([WinkAC(climate, hass, temp_unit)]) add_devices([WinkAC(climate, hass)])
for water_heater in pywink.get_water_heaters():
_id = water_heater.object_id() + water_heater.name()
if _id not in hass.data[DOMAIN]['unique_ids']:
add_devices([WinkWaterHeater(water_heater, hass)])
# pylint: disable=abstract-method # pylint: disable=abstract-method
class WinkThermostat(WinkDevice, ClimateDevice): class WinkThermostat(WinkDevice, ClimateDevice):
"""Representation of a Wink thermostat.""" """Representation of a Wink thermostat."""
def __init__(self, wink, hass, temp_unit):
"""Initialize the Wink device."""
super().__init__(wink, hass)
self._config_temp_unit = temp_unit
@asyncio.coroutine @asyncio.coroutine
def async_added_to_hass(self): def async_added_to_hass(self):
"""Callback when entity is added to hass.""" """Callback when entity is added to hass."""
@ -139,18 +152,12 @@ class WinkThermostat(WinkDevice, ClimateDevice):
"""Return current operation ie. heat, cool, idle.""" """Return current operation ie. heat, cool, idle."""
if not self.wink.is_on(): if not self.wink.is_on():
current_op = STATE_OFF current_op = STATE_OFF
elif self.wink.current_hvac_mode() == 'cool_only':
current_op = STATE_COOL
elif self.wink.current_hvac_mode() == 'heat_only':
current_op = STATE_HEAT
elif self.wink.current_hvac_mode() == 'aux':
current_op = STATE_HEAT
elif self.wink.current_hvac_mode() == 'auto':
current_op = STATE_AUTO
elif self.wink.current_hvac_mode() == 'eco':
current_op = STATE_ECO
else: else:
current_op = STATE_UNKNOWN current_op = WINK_STATE_TO_HA.get(self.wink.current_hvac_mode())
if current_op == 'aux':
return STATE_HEAT
if current_op is None:
current_op = STATE_UNKNOWN
return current_op return current_op
@property @property
@ -199,11 +206,12 @@ class WinkThermostat(WinkDevice, ClimateDevice):
@property @property
def is_aux_heat_on(self): def is_aux_heat_on(self):
"""Return true if aux heater.""" """Return true if aux heater."""
if self.wink.current_hvac_mode() == 'aux' and self.wink.is_on(): if 'aux' not in self.wink.hvac_modes():
return None
if self.wink.current_hvac_mode() == 'aux':
return True return True
elif self.wink.current_hvac_mode() == 'aux' and not self.wink.is_on(): return False
return False
return None
def set_temperature(self, **kwargs): def set_temperature(self, **kwargs):
"""Set new target temperature.""" """Set new target temperature."""
@ -223,32 +231,27 @@ class WinkThermostat(WinkDevice, ClimateDevice):
def set_operation_mode(self, operation_mode): def set_operation_mode(self, operation_mode):
"""Set operation mode.""" """Set operation mode."""
if operation_mode == STATE_HEAT: op_mode_to_set = HA_STATE_TO_WINK.get(operation_mode)
self.wink.set_operation_mode('heat_only') # The only way to disable aux heat is with the toggle
elif operation_mode == STATE_COOL: if self.is_aux_heat_on and op_mode_to_set == STATE_HEAT:
self.wink.set_operation_mode('cool_only') return
elif operation_mode == STATE_AUTO: self.wink.set_operation_mode(op_mode_to_set)
self.wink.set_operation_mode('auto')
elif operation_mode == STATE_OFF:
self.wink.set_operation_mode('off')
elif operation_mode == STATE_AUX:
self.wink.set_operation_mode('aux')
elif operation_mode == STATE_ECO:
self.wink.set_operation_mode('eco')
@property @property
def operation_list(self): def operation_list(self):
"""List of available operation modes.""" """List of available operation modes."""
op_list = ['off'] op_list = ['off']
modes = self.wink.hvac_modes() modes = self.wink.hvac_modes()
if 'cool_only' in modes: for mode in modes:
op_list.append(STATE_COOL) if mode == 'aux':
if 'heat_only' in modes or 'aux' in modes: continue
op_list.append(STATE_HEAT) ha_mode = WINK_STATE_TO_HA.get(mode)
if 'auto' in modes: if ha_mode is not None:
op_list.append(STATE_AUTO) op_list.append(ha_mode)
if 'eco' in modes: else:
op_list.append(STATE_ECO) error = "Invaid operation mode mapping. " + mode + \
" doesn't map. Please report this."
_LOGGER.error(error)
return op_list return op_list
def turn_away_mode_on(self): def turn_away_mode_on(self):
@ -282,11 +285,11 @@ class WinkThermostat(WinkDevice, ClimateDevice):
def turn_aux_heat_on(self): def turn_aux_heat_on(self):
"""Turn auxiliary heater on.""" """Turn auxiliary heater on."""
self.set_operation_mode(STATE_AUX) self.wink.set_operation_mode('aux')
def turn_aux_heat_off(self): def turn_aux_heat_off(self):
"""Turn auxiliary heater off.""" """Turn auxiliary heater off."""
self.set_operation_mode(STATE_AUTO) self.set_operation_mode(STATE_HEAT)
@property @property
def min_temp(self): def min_temp(self):
@ -344,11 +347,6 @@ class WinkThermostat(WinkDevice, ClimateDevice):
class WinkAC(WinkDevice, ClimateDevice): class WinkAC(WinkDevice, ClimateDevice):
"""Representation of a Wink air conditioner.""" """Representation of a Wink air conditioner."""
def __init__(self, wink, hass, temp_unit):
"""Initialize the Wink device."""
super().__init__(wink, hass)
self._config_temp_unit = temp_unit
@property @property
def temperature_unit(self): def temperature_unit(self):
"""Return the unit of measurement.""" """Return the unit of measurement."""
@ -382,14 +380,10 @@ class WinkAC(WinkDevice, ClimateDevice):
"""Return current operation ie. heat, cool, idle.""" """Return current operation ie. heat, cool, idle."""
if not self.wink.is_on(): if not self.wink.is_on():
current_op = STATE_OFF current_op = STATE_OFF
elif self.wink.current_mode() == 'cool_only':
current_op = STATE_COOL
elif self.wink.current_mode() == 'auto_eco':
current_op = STATE_ECO
elif self.wink.current_mode() == 'fan_only':
current_op = STATE_FAN
else: else:
current_op = STATE_UNKNOWN current_op = WINK_STATE_TO_HA.get(self.wink.current_hvac_mode())
if current_op is None:
current_op = STATE_UNKNOWN
return current_op return current_op
@property @property
@ -397,12 +391,14 @@ class WinkAC(WinkDevice, ClimateDevice):
"""List of available operation modes.""" """List of available operation modes."""
op_list = ['off'] op_list = ['off']
modes = self.wink.modes() modes = self.wink.modes()
if 'cool_only' in modes: for mode in modes:
op_list.append(STATE_COOL) ha_mode = WINK_STATE_TO_HA.get(mode)
if 'auto_eco' in modes: if ha_mode is not None:
op_list.append(STATE_ECO) op_list.append(ha_mode)
if 'fan_only' in modes: else:
op_list.append(STATE_FAN) error = "Invaid operation mode mapping. " + mode + \
" doesn't map. Please report this."
_LOGGER.error(error)
return op_list return op_list
def set_temperature(self, **kwargs): def set_temperature(self, **kwargs):
@ -412,30 +408,16 @@ class WinkAC(WinkDevice, ClimateDevice):
def set_operation_mode(self, operation_mode): def set_operation_mode(self, operation_mode):
"""Set operation mode.""" """Set operation mode."""
if operation_mode == STATE_COOL: op_mode_to_set = HA_STATE_TO_WINK.get(operation_mode)
self.wink.set_operation_mode('cool_only') if op_mode_to_set == 'eco':
elif operation_mode == STATE_ECO: op_mode_to_set = 'auto_eco'
self.wink.set_operation_mode('auto_eco') self.wink.set_operation_mode(op_mode_to_set)
elif operation_mode == STATE_OFF:
self.wink.set_operation_mode('off')
elif operation_mode == STATE_FAN:
self.wink.set_operation_mode('fan_only')
@property @property
def target_temperature(self): def target_temperature(self):
"""Return the temperature we try to reach.""" """Return the temperature we try to reach."""
return self.wink.current_max_set_point() return self.wink.current_max_set_point()
@property
def target_temperature_low(self):
"""Only supports cool."""
return None
@property
def target_temperature_high(self):
"""Only supports cool."""
return None
@property @property
def current_fan_mode(self): def current_fan_mode(self):
"""Return the current fan mode.""" """Return the current fan mode."""
@ -453,12 +435,97 @@ class WinkAC(WinkDevice, ClimateDevice):
"""Return a list of available fan modes.""" """Return a list of available fan modes."""
return [SPEED_LOW, SPEED_MEDIUM, SPEED_HIGH] return [SPEED_LOW, SPEED_MEDIUM, SPEED_HIGH]
def set_fan_mode(self, mode): def set_fan_mode(self, fan):
"""Set fan speed.""" """Set fan speed."""
if mode == SPEED_LOW: if fan == SPEED_LOW:
speed = 0.4 speed = 0.4
elif mode == SPEED_MEDIUM: elif fan == SPEED_MEDIUM:
speed = 0.8 speed = 0.8
elif mode == SPEED_HIGH: elif fan == SPEED_HIGH:
speed = 1.0 speed = 1.0
self.wink.set_ac_fan_speed(speed) self.wink.set_ac_fan_speed(speed)
class WinkWaterHeater(WinkDevice, ClimateDevice):
"""Representation of a Wink water heater."""
@property
def temperature_unit(self):
"""Return the unit of measurement."""
# The Wink API always returns temp in Celsius
return TEMP_CELSIUS
@property
def device_state_attributes(self):
"""Return the optional state attributes."""
data = {}
data["vacation_mode"] = self.wink.vacation_mode_enabled()
data["rheem_type"] = self.wink.rheem_type()
return data
@property
def current_operation(self):
"""
Return current operation one of the following.
["eco", "performance", "heat_pump",
"high_demand", "electric_only", "gas]
"""
if not self.wink.is_on():
current_op = STATE_OFF
else:
current_op = WINK_STATE_TO_HA.get(self.wink.current_mode())
if current_op is None:
current_op = STATE_UNKNOWN
return current_op
@property
def operation_list(self):
"""List of available operation modes."""
op_list = ['off']
modes = self.wink.modes()
for mode in modes:
if mode == 'aux':
continue
ha_mode = WINK_STATE_TO_HA.get(mode)
if ha_mode is not None:
op_list.append(ha_mode)
else:
error = "Invaid operation mode mapping. " + mode + \
" doesn't map. Please report this."
_LOGGER.error(error)
return op_list
def set_temperature(self, **kwargs):
"""Set new target temperature."""
target_temp = kwargs.get(ATTR_TEMPERATURE)
self.wink.set_temperature(target_temp)
def set_operation_mode(self, operation_mode):
"""Set operation mode."""
op_mode_to_set = HA_STATE_TO_WINK.get(operation_mode)
self.wink.set_operation_mode(op_mode_to_set)
@property
def target_temperature(self):
"""Return the temperature we try to reach."""
return self.wink.current_set_point()
def turn_away_mode_on(self):
"""Turn away on."""
self.wink.set_vacation_mode(True)
def turn_away_mode_off(self):
"""Turn away off."""
self.wink.set_vacation_mode(False)
@property
def min_temp(self):
"""Return the minimum temperature."""
return self.wink.min_set_point()
@property
def max_temp(self):
"""Return the maximum temperature."""
return self.wink.max_set_point()

View File

@ -83,7 +83,7 @@ wink_set_lock_vacation_mode:
description: Name of lock to unlock description: Name of lock to unlock
example: 'lock.front_door' example: 'lock.front_door'
enabled: enabled:
description: enable or disable. true or false. description: enable or disable. true or false.
example: true example: true
wink_set_lock_alarm_mode: wink_set_lock_alarm_mode:
@ -94,7 +94,7 @@ wink_set_lock_alarm_mode:
description: Name of lock to unlock description: Name of lock to unlock
example: 'lock.front_door' example: 'lock.front_door'
mode: mode:
description: One of tamper, activity, or forced_entry description: One of tamper, activity, or forced_entry
example: tamper example: tamper
wink_set_lock_alarm_sensitivity: wink_set_lock_alarm_sensitivity:
@ -105,7 +105,7 @@ wink_set_lock_alarm_sensitivity:
description: Name of lock to unlock description: Name of lock to unlock
example: 'lock.front_door' example: 'lock.front_door'
sensitivity: sensitivity:
description: One of low, medium_low, medium, medium_high, high description: One of low, medium_low, medium, medium_high, high
example: medium example: medium
wink_set_lock_alarm_state: wink_set_lock_alarm_state:
@ -116,7 +116,7 @@ wink_set_lock_alarm_state:
description: Name of lock to unlock description: Name of lock to unlock
example: 'lock.front_door' example: 'lock.front_door'
enabled: enabled:
description: enable or disable. true or false. description: enable or disable. true or false.
example: true example: true
wink_set_lock_beeper_state: wink_set_lock_beeper_state:
@ -127,6 +127,19 @@ wink_set_lock_beeper_state:
description: Name of lock to unlock description: Name of lock to unlock
example: 'lock.front_door' example: 'lock.front_door'
enabled: enabled:
description: enable or disable. true or false. description: enable or disable. true or false.
example: true example: true
wink_add_new_lock_key_code:
description: Add a new user key code.
fields:
entity_id:
description: Name of lock to unlock
example: 'lock.front_door'
name:
description: name of the new key code.
example: Bob
code:
description: new key code, length must match length of other codes. Default length is 4.
example: 1234

View File

@ -13,7 +13,7 @@ import voluptuous as vol
from homeassistant.components.lock import LockDevice from homeassistant.components.lock import LockDevice
from homeassistant.components.wink import WinkDevice, DOMAIN from homeassistant.components.wink import WinkDevice, DOMAIN
import homeassistant.helpers.config_validation as cv import homeassistant.helpers.config_validation as cv
from homeassistant.const import ATTR_ENTITY_ID, STATE_UNKNOWN from homeassistant.const import ATTR_ENTITY_ID, STATE_UNKNOWN, ATTR_CODE
from homeassistant.config import load_yaml_config_file from homeassistant.config import load_yaml_config_file
DEPENDENCIES = ['wink'] DEPENDENCIES = ['wink']
@ -25,10 +25,12 @@ SERVICE_SET_ALARM_MODE = 'wink_set_lock_alarm_mode'
SERVICE_SET_ALARM_SENSITIVITY = 'wink_set_lock_alarm_sensitivity' SERVICE_SET_ALARM_SENSITIVITY = 'wink_set_lock_alarm_sensitivity'
SERVICE_SET_ALARM_STATE = 'wink_set_lock_alarm_state' SERVICE_SET_ALARM_STATE = 'wink_set_lock_alarm_state'
SERVICE_SET_BEEPER_STATE = 'wink_set_lock_beeper_state' SERVICE_SET_BEEPER_STATE = 'wink_set_lock_beeper_state'
SERVICE_ADD_KEY = 'wink_add_new_lock_key_code'
ATTR_ENABLED = 'enabled' ATTR_ENABLED = 'enabled'
ATTR_SENSITIVITY = 'sensitivity' ATTR_SENSITIVITY = 'sensitivity'
ATTR_MODE = 'mode' ATTR_MODE = 'mode'
ATTR_NAME = 'name'
ALARM_SENSITIVITY_MAP = {"low": 0.2, "medium_low": 0.4, ALARM_SENSITIVITY_MAP = {"low": 0.2, "medium_low": 0.4,
"medium": 0.6, "medium_high": 0.8, "medium": 0.6, "medium_high": 0.8,
@ -53,6 +55,12 @@ SET_ALARM_MODES_SCHEMA = vol.Schema({
vol.Required(ATTR_MODE): vol.In(ALARM_MODES_MAP) vol.Required(ATTR_MODE): vol.In(ALARM_MODES_MAP)
}) })
ADD_KEY_SCHEMA = vol.Schema({
vol.Optional(ATTR_ENTITY_ID): cv.entity_ids,
vol.Required(ATTR_NAME): cv.string,
vol.Required(ATTR_CODE): cv.positive_int,
})
def setup_platform(hass, config, add_devices, discovery_info=None): def setup_platform(hass, config, add_devices, discovery_info=None):
"""Set up the Wink platform.""" """Set up the Wink platform."""
@ -86,6 +94,10 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
lock.set_alarm_mode(service.data.get(ATTR_MODE)) lock.set_alarm_mode(service.data.get(ATTR_MODE))
elif service.service == SERVICE_SET_ALARM_SENSITIVITY: elif service.service == SERVICE_SET_ALARM_SENSITIVITY:
lock.set_alarm_sensitivity(service.data.get(ATTR_SENSITIVITY)) lock.set_alarm_sensitivity(service.data.get(ATTR_SENSITIVITY))
elif service.service == SERVICE_ADD_KEY:
name = service.data.get(ATTR_NAME)
code = service.data.get(ATTR_CODE)
lock.add_new_key(code, name)
descriptions = load_yaml_config_file( descriptions = load_yaml_config_file(
path.join(path.dirname(__file__), 'services.yaml')) path.join(path.dirname(__file__), 'services.yaml'))
@ -115,6 +127,11 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
descriptions.get(SERVICE_SET_ALARM_SENSITIVITY), descriptions.get(SERVICE_SET_ALARM_SENSITIVITY),
schema=SET_SENSITIVITY_SCHEMA) schema=SET_SENSITIVITY_SCHEMA)
hass.services.register(DOMAIN, SERVICE_ADD_KEY,
service_handle,
descriptions.get(SERVICE_ADD_KEY),
schema=ADD_KEY_SCHEMA)
class WinkLockDevice(WinkDevice, LockDevice): class WinkLockDevice(WinkDevice, LockDevice):
"""Representation of a Wink lock.""" """Representation of a Wink lock."""
@ -149,6 +166,10 @@ class WinkLockDevice(WinkDevice, LockDevice):
"""Set lock's beeper mode.""" """Set lock's beeper mode."""
self.wink.set_beeper_mode(enabled) self.wink.set_beeper_mode(enabled)
def add_new_key(self, code, name):
"""Add a new user key code."""
self.wink.add_new_key(code, name)
def set_alarm_sensitivity(self, sensitivity): def set_alarm_sensitivity(self, sensitivity):
""" """
Set lock's alarm sensitivity. Set lock's alarm sensitivity.
@ -176,14 +197,14 @@ class WinkLockDevice(WinkDevice, LockDevice):
super_attrs = super().device_state_attributes super_attrs = super().device_state_attributes
sensitivity = dict_value_to_key(ALARM_SENSITIVITY_MAP, sensitivity = dict_value_to_key(ALARM_SENSITIVITY_MAP,
self.wink.alarm_sensitivity()) self.wink.alarm_sensitivity())
super_attrs['alarm sensitivity'] = sensitivity super_attrs['alarm_sensitivity'] = sensitivity
super_attrs['vacation mode'] = self.wink.vacation_mode_enabled() super_attrs['vacation_mode'] = self.wink.vacation_mode_enabled()
super_attrs['beeper mode'] = self.wink.beeper_enabled() super_attrs['beeper_mode'] = self.wink.beeper_enabled()
super_attrs['auto lock'] = self.wink.auto_lock_enabled() super_attrs['auto_lock'] = self.wink.auto_lock_enabled()
alarm_mode = dict_value_to_key(ALARM_MODES_MAP, alarm_mode = dict_value_to_key(ALARM_MODES_MAP,
self.wink.alarm_mode()) self.wink.alarm_mode())
super_attrs['alarm mode'] = alarm_mode super_attrs['alarm_mode'] = alarm_mode
super_attrs['alarm enabled'] = self.wink.alarm_enabled() super_attrs['alarm_enabled'] = self.wink.alarm_enabled()
return super_attrs return super_attrs

View File

@ -1,3 +1,4 @@
foursquare: foursquare:
checkin: checkin:
description: Check a user into a Foursquare venue description: Check a user into a Foursquare venue
@ -115,7 +116,7 @@ persistent_notification:
notification_id: notification_id:
description: Target ID of the notification, will replace a notification with the same Id. [Optional] description: Target ID of the notification, will replace a notification with the same Id. [Optional]
example: 1234 example: 1234
dismiss: dismiss:
description: Remove a notification from the frontend description: Remove a notification from the frontend
@ -580,22 +581,22 @@ abode:
setting: setting:
description: Setting to change. description: Setting to change.
example: 'beeper_mute' example: 'beeper_mute'
value: value:
description: Value of the setting. description: Value of the setting.
example: '1' example: '1'
capture_image: capture_image:
description: Request a new image capture from a camera device. description: Request a new image capture from a camera device.
fields: fields:
entity_id: entity_id:
description: Entity id of the camera to request an image. description: Entity id of the camera to request an image.
example: 'camera.downstairs_motion_camera' example: 'camera.downstairs_motion_camera'
trigger_quick_action: trigger_quick_action:
description: Trigger an Abode quick action. description: Trigger an Abode quick action.
fields: fields:
entity_id: entity_id:
description: Entity id of the quick action to trigger. description: Entity id of the quick action to trigger.
@ -620,8 +621,47 @@ input_boolean:
turn_on: turn_on:
description: Turns ON an input boolean description: Turns ON an input boolean
fields: fields:
entity_id: entity_id:
description: Entity id of the input boolean to turn on description: Entity id of the input boolean to turn on
example: 'input_boolean.notify_alerts' example: 'input_boolean.notify_alerts'
wink:
pair_new_device:
description: Pair a new device to a Wink Hub.
fields:
hub_name:
description: The name of the hub to pair a new device to.
example: 'My hub'
pairing_mode:
description: One of ["zigbee", "zwave", "zwave_exclusion", "zwave_network_rediscovery", "lutron", "bluetooth", "kidde"]
example: 'zigbee'
kidde_radio_code:
description: A string of 8 1s and 0s one for each dip switch on the kidde device left --> right = 1 --> 8
example: '10101010'
rename_wink_device:
description: Rename the provided device.
fields:
entity_id:
description: The entity_id of the device to rename.
example: binary_sensor.front_door_opened
name:
description: The name to change it to.
example: back_door
delete_wink_device:
description: Remove/unpair device from Wink.
fields:
entity_id:
description: The entity_id of the device to delete.
pull_newly_added_devices_from_wink:
description: Pull newly pair devices from Wink.
refresh_state_from_wink:
description: Pull the latest states for every device.

View File

@ -20,11 +20,12 @@ from homeassistant.helpers.event import track_time_interval
from homeassistant.const import ( from homeassistant.const import (
ATTR_BATTERY_LEVEL, CONF_EMAIL, CONF_PASSWORD, ATTR_BATTERY_LEVEL, CONF_EMAIL, CONF_PASSWORD,
EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_START,
EVENT_HOMEASSISTANT_STOP, __version__) EVENT_HOMEASSISTANT_STOP, __version__, ATTR_ENTITY_ID)
from homeassistant.helpers.entity import Entity from homeassistant.helpers.entity import Entity
import homeassistant.helpers.config_validation as cv import homeassistant.helpers.config_validation as cv
from homeassistant.config import load_yaml_config_file
REQUIREMENTS = ['python-wink==1.5.1', 'pubnubsub-handler==1.0.2'] REQUIREMENTS = ['python-wink==1.6.0', 'pubnubsub-handler==1.0.2']
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -45,6 +46,10 @@ ATTR_ACCESS_TOKEN = 'access_token'
ATTR_REFRESH_TOKEN = 'refresh_token' ATTR_REFRESH_TOKEN = 'refresh_token'
ATTR_CLIENT_ID = 'client_id' ATTR_CLIENT_ID = 'client_id'
ATTR_CLIENT_SECRET = 'client_secret' ATTR_CLIENT_SECRET = 'client_secret'
ATTR_NAME = 'name'
ATTR_PAIRING_MODE = 'pairing_mode'
ATTR_KIDDE_RADIO_CODE = 'kidde_radio_code'
ATTR_HUB_NAME = 'hub_name'
WINK_AUTH_CALLBACK_PATH = '/auth/wink/callback' WINK_AUTH_CALLBACK_PATH = '/auth/wink/callback'
WINK_AUTH_START = '/auth/wink' WINK_AUTH_START = '/auth/wink'
@ -56,9 +61,12 @@ DEFAULT_CONFIG = {
'client_secret': 'CLIENT_SECRET_HERE' 'client_secret': 'CLIENT_SECRET_HERE'
} }
SERVICE_ADD_NEW_DEVICES = 'add_new_devices' SERVICE_ADD_NEW_DEVICES = 'pull_newly_added_devices_from_wink'
SERVICE_REFRESH_STATES = 'refresh_state_from_wink' SERVICE_REFRESH_STATES = 'refresh_state_from_wink'
SERVICE_KEEP_ALIVE = 'keep_pubnub_updates_flowing' SERVICE_RENAME_DEVICE = 'rename_wink_device'
SERVICE_DELETE_DEVICE = 'delete_wink_device'
SERVICE_SET_PAIRING_MODE = 'pair_new_device'
CONFIG_SCHEMA = vol.Schema({ CONFIG_SCHEMA = vol.Schema({
DOMAIN: vol.Schema({ DOMAIN: vol.Schema({
@ -74,11 +82,29 @@ CONFIG_SCHEMA = vol.Schema({
}) })
}, extra=vol.ALLOW_EXTRA) }, extra=vol.ALLOW_EXTRA)
RENAME_DEVICE_SCHEMA = vol.Schema({
vol.Required(ATTR_ENTITY_ID): cv.entity_ids,
vol.Required(ATTR_NAME): cv.string
}, extra=vol.ALLOW_EXTRA)
DELETE_DEVICE_SCHEMA = vol.Schema({
vol.Required(ATTR_ENTITY_ID): cv.entity_ids
}, extra=vol.ALLOW_EXTRA)
SET_PAIRING_MODE_SCHEMA = vol.Schema({
vol.Required(ATTR_HUB_NAME): cv.string,
vol.Required(ATTR_PAIRING_MODE): cv.string,
vol.Optional(ATTR_KIDDE_RADIO_CODE): cv.string
}, extra=vol.ALLOW_EXTRA)
WINK_COMPONENTS = [ WINK_COMPONENTS = [
'binary_sensor', 'sensor', 'light', 'switch', 'lock', 'cover', 'climate', 'binary_sensor', 'sensor', 'light', 'switch', 'lock', 'cover', 'climate',
'fan', 'alarm_control_panel', 'scene' 'fan', 'alarm_control_panel', 'scene'
] ]
WINK_HUBS = []
def _write_config_file(file_path, config): def _write_config_file(file_path, config):
try: try:
@ -177,6 +203,9 @@ def setup(hass, config):
import pywink import pywink
from pubnubsubhandler import PubNubSubscriptionHandler from pubnubsubhandler import PubNubSubscriptionHandler
descriptions = load_yaml_config_file(
os.path.join(os.path.dirname(__file__), 'services.yaml')).get(DOMAIN)
if hass.data.get(DOMAIN) is None: if hass.data.get(DOMAIN) is None:
hass.data[DOMAIN] = { hass.data[DOMAIN] = {
'unique_ids': [], 'unique_ids': [],
@ -313,6 +342,7 @@ def setup(hass, config):
def stop_subscription(event): def stop_subscription(event):
"""Stop the pubnub subscription.""" """Stop the pubnub subscription."""
hass.data[DOMAIN]['pubnub'].unsubscribe() hass.data[DOMAIN]['pubnub'].unsubscribe()
hass.data[DOMAIN]['pubnub'] = None
hass.bus.listen(EVENT_HOMEASSISTANT_STOP, stop_subscription) hass.bus.listen(EVENT_HOMEASSISTANT_STOP, stop_subscription)
@ -333,7 +363,9 @@ def setup(hass, config):
for entity in entity_list: for entity in entity_list:
time.sleep(1) time.sleep(1)
entity.schedule_update_ha_state(True) entity.schedule_update_ha_state(True)
hass.services.register(DOMAIN, SERVICE_REFRESH_STATES, force_update)
hass.services.register(DOMAIN, SERVICE_REFRESH_STATES, force_update,
descriptions.get(SERVICE_REFRESH_STATES))
def pull_new_devices(call): def pull_new_devices(call):
"""Pull new devices added to users Wink account since startup.""" """Pull new devices added to users Wink account since startup."""
@ -341,12 +373,71 @@ def setup(hass, config):
for _component in WINK_COMPONENTS: for _component in WINK_COMPONENTS:
discovery.load_platform(hass, _component, DOMAIN, {}, config) discovery.load_platform(hass, _component, DOMAIN, {}, config)
hass.services.register(DOMAIN, SERVICE_ADD_NEW_DEVICES, pull_new_devices) hass.services.register(DOMAIN, SERVICE_ADD_NEW_DEVICES, pull_new_devices,
descriptions.get(SERVICE_ADD_NEW_DEVICES))
def set_pairing_mode(call):
"""Put the hub in provided pairing mode."""
hub_name = call.data.get('hub_name')
pairing_mode = call.data.get('pairing_mode')
kidde_code = call.data.get('kidde_radio_code')
for hub in WINK_HUBS:
if hub.name() == hub_name:
hub.pair_new_device(pairing_mode,
kidde_radio_code=kidde_code)
def rename_device(call):
"""Set specified device's name."""
# This should only be called on one device at a time.
found_device = None
entity_id = call.data.get('entity_id')[0]
all_devices = []
for list_of_devices in hass.data[DOMAIN]['entities'].values():
all_devices += list_of_devices
for device in all_devices:
if device.entity_id == entity_id:
found_device = device
if found_device is not None:
name = call.data.get('name')
found_device.wink.set_name(name)
hass.services.register(DOMAIN, SERVICE_RENAME_DEVICE, rename_device,
descriptions.get(SERVICE_RENAME_DEVICE),
schema=RENAME_DEVICE_SCHEMA)
def delete_device(call):
"""Delete specified device."""
# This should only be called on one device at a time.
found_device = None
entity_id = call.data.get('entity_id')[0]
all_devices = []
for list_of_devices in hass.data[DOMAIN]['entities'].values():
all_devices += list_of_devices
for device in all_devices:
if device.entity_id == entity_id:
found_device = device
if found_device is not None:
found_device.wink.remove_device()
hass.services.register(DOMAIN, SERVICE_DELETE_DEVICE, delete_device,
descriptions.get(SERVICE_DELETE_DEVICE),
schema=DELETE_DEVICE_SCHEMA)
hubs = pywink.get_hubs()
for hub in hubs:
if hub.device_manufacturer() == 'wink':
WINK_HUBS.append(hub)
if WINK_HUBS:
hass.services.register(
DOMAIN, SERVICE_SET_PAIRING_MODE, set_pairing_mode,
descriptions.get(SERVICE_SET_PAIRING_MODE),
schema=SET_PAIRING_MODE_SCHEMA)
# Load components for the devices in Wink that we support # Load components for the devices in Wink that we support
for component in WINK_COMPONENTS: for wink_component in WINK_COMPONENTS:
hass.data[DOMAIN]['entities'][component] = [] hass.data[DOMAIN]['entities'][wink_component] = []
discovery.load_platform(hass, component, DOMAIN, {}, config) discovery.load_platform(hass, wink_component, DOMAIN, {}, config)
return True return True

View File

@ -807,7 +807,7 @@ python-velbus==2.0.11
python-vlc==1.1.2 python-vlc==1.1.2
# homeassistant.components.wink # homeassistant.components.wink
python-wink==1.5.1 python-wink==1.6.0
# homeassistant.components.sensor.swiss_public_transport # homeassistant.components.sensor.swiss_public_transport
python_opendata_transport==0.0.2 python_opendata_transport==0.0.2