Remove deprecated Wink integration (#57634)

pull/57363/head^2
Franck Nijhof 2021-10-14 00:20:13 +02:00 committed by GitHub
parent b54fc0229d
commit 45f3eb6991
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 0 additions and 3042 deletions

View File

@ -1191,7 +1191,6 @@ omit =
homeassistant/components/webostv/*
homeassistant/components/whois/sensor.py
homeassistant/components/wiffi/*
homeassistant/components/wink/*
homeassistant/components/wirelesstag/*
homeassistant/components/wolflink/__init__.py
homeassistant/components/wolflink/sensor.py

View File

@ -38,7 +38,6 @@ SERVICE_SAMSUNG_PRINTER = "samsung_printer"
SERVICE_TELLDUSLIVE = "tellstick"
SERVICE_YEELIGHT = "yeelight"
SERVICE_WEMO = "belkin_wemo"
SERVICE_WINK = "wink"
SERVICE_XIAOMI_GW = "xiaomi_gw"
# These have custom protocols
@ -94,7 +93,6 @@ MIGRATED_SERVICE_HANDLERS = [
"sonos",
"songpal",
SERVICE_WEMO,
SERVICE_WINK,
SERVICE_XIAOMI_GW,
"volumio",
SERVICE_YEELIGHT,

View File

@ -1,971 +0,0 @@
"""Support for Wink hubs."""
from __future__ import annotations
from datetime import timedelta
import json
import logging
import os
import time
from typing import Any
from aiohttp.web import Response
from pubnubsubhandler import PubNubSubscriptionHandler
import pywink
import voluptuous as vol
from homeassistant.components.http import HomeAssistantView
from homeassistant.const import (
ATTR_BATTERY_LEVEL,
ATTR_NAME,
CONF_CLIENT_ID,
CONF_CLIENT_SECRET,
CONF_EMAIL,
CONF_PASSWORD,
EVENT_HOMEASSISTANT_START,
EVENT_HOMEASSISTANT_STOP,
STATE_OFF,
STATE_ON,
__version__,
)
from homeassistant.core import callback
from homeassistant.helpers import discovery
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.config_validation import make_entity_service_schema
from homeassistant.helpers.entity import Entity
from homeassistant.helpers.entity_component import EntityComponent
from homeassistant.helpers.event import track_time_interval
from homeassistant.helpers.network import get_url
from homeassistant.util.json import load_json, save_json
_LOGGER = logging.getLogger(__name__)
DOMAIN = "wink"
SUBSCRIPTION_HANDLER = None
CONF_USER_AGENT = "user_agent"
CONF_OAUTH = "oauth"
CONF_LOCAL_CONTROL = "local_control"
CONF_MISSING_OAUTH_MSG = "Missing oauth2 credentials."
ATTR_ACCESS_TOKEN = "access_token"
ATTR_REFRESH_TOKEN = "refresh_token"
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_START = "/auth/wink"
WINK_CONFIG_FILE = ".wink.conf"
USER_AGENT = f"Manufacturer/Home-Assistant{__version__} python/3 Wink/3"
DEFAULT_CONFIG = {
CONF_CLIENT_ID: "CLIENT_ID_HERE",
CONF_CLIENT_SECRET: "CLIENT_SECRET_HERE",
}
SERVICE_ADD_NEW_DEVICES = "pull_newly_added_devices_from_wink"
SERVICE_REFRESH_STATES = "refresh_state_from_wink"
SERVICE_RENAME_DEVICE = "rename_wink_device"
SERVICE_DELETE_DEVICE = "delete_wink_device"
SERVICE_SET_PAIRING_MODE = "pair_new_device"
SERVICE_SET_CHIME_VOLUME = "set_chime_volume"
SERVICE_SET_SIREN_VOLUME = "set_siren_volume"
SERVICE_ENABLE_CHIME = "enable_chime"
SERVICE_SET_SIREN_TONE = "set_siren_tone"
SERVICE_SET_AUTO_SHUTOFF = "siren_set_auto_shutoff"
SERVICE_SIREN_STROBE_ENABLED = "set_siren_strobe_enabled"
SERVICE_CHIME_STROBE_ENABLED = "set_chime_strobe_enabled"
SERVICE_ENABLE_SIREN = "enable_siren"
SERVICE_SET_DIAL_CONFIG = "set_nimbus_dial_configuration"
SERVICE_SET_DIAL_STATE = "set_nimbus_dial_state"
ATTR_VOLUME = "volume"
ATTR_TONE = "tone"
ATTR_ENABLED = "enabled"
ATTR_AUTO_SHUTOFF = "auto_shutoff"
ATTR_MIN_VALUE = "min_value"
ATTR_MAX_VALUE = "max_value"
ATTR_ROTATION = "rotation"
ATTR_SCALE = "scale"
ATTR_TICKS = "ticks"
ATTR_MIN_POSITION = "min_position"
ATTR_MAX_POSITION = "max_position"
ATTR_VALUE = "value"
ATTR_LABELS = "labels"
SCALES = ["linear", "log"]
ROTATIONS = ["cw", "ccw"]
VOLUMES = ["low", "medium", "high"]
TONES = [
"doorbell",
"fur_elise",
"doorbell_extended",
"alert",
"william_tell",
"rondo_alla_turca",
"police_siren",
"evacuation",
"beep_beep",
"beep",
]
CHIME_TONES = TONES + ["inactive"]
AUTO_SHUTOFF_TIMES = [None, -1, 30, 60, 120]
CONFIG_SCHEMA = vol.Schema(
vol.All(
cv.deprecated(DOMAIN),
{
DOMAIN: vol.Schema(
{
vol.Inclusive(
CONF_EMAIL, CONF_OAUTH, msg=CONF_MISSING_OAUTH_MSG
): cv.string,
vol.Inclusive(
CONF_PASSWORD, CONF_OAUTH, msg=CONF_MISSING_OAUTH_MSG
): cv.string,
vol.Inclusive(
CONF_CLIENT_ID, CONF_OAUTH, msg=CONF_MISSING_OAUTH_MSG
): cv.string,
vol.Inclusive(
CONF_CLIENT_SECRET, CONF_OAUTH, msg=CONF_MISSING_OAUTH_MSG
): cv.string,
vol.Optional(CONF_LOCAL_CONTROL, default=False): cv.boolean,
}
),
},
),
extra=vol.ALLOW_EXTRA,
)
RENAME_DEVICE_SCHEMA = make_entity_service_schema(
{vol.Required(ATTR_NAME): cv.string}, extra=vol.ALLOW_EXTRA
)
DELETE_DEVICE_SCHEMA = make_entity_service_schema({}, 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,
)
SET_VOLUME_SCHEMA = make_entity_service_schema(
{vol.Required(ATTR_VOLUME): vol.In(VOLUMES)}
)
SET_SIREN_TONE_SCHEMA = make_entity_service_schema(
{vol.Required(ATTR_TONE): vol.In(TONES)}
)
SET_CHIME_MODE_SCHEMA = make_entity_service_schema(
{vol.Required(ATTR_TONE): vol.In(CHIME_TONES)}
)
SET_AUTO_SHUTOFF_SCHEMA = make_entity_service_schema(
{vol.Required(ATTR_AUTO_SHUTOFF): vol.In(AUTO_SHUTOFF_TIMES)}
)
SET_STROBE_ENABLED_SCHEMA = make_entity_service_schema(
{vol.Required(ATTR_ENABLED): cv.boolean}
)
ENABLED_SIREN_SCHEMA = make_entity_service_schema(
{vol.Required(ATTR_ENABLED): cv.boolean}
)
DIAL_CONFIG_SCHEMA = make_entity_service_schema(
{
vol.Optional(ATTR_MIN_VALUE): vol.Coerce(int),
vol.Optional(ATTR_MAX_VALUE): vol.Coerce(int),
vol.Optional(ATTR_MIN_POSITION): cv.positive_int,
vol.Optional(ATTR_MAX_POSITION): cv.positive_int,
vol.Optional(ATTR_ROTATION): vol.In(ROTATIONS),
vol.Optional(ATTR_SCALE): vol.In(SCALES),
vol.Optional(ATTR_TICKS): cv.positive_int,
}
)
DIAL_STATE_SCHEMA = make_entity_service_schema(
{
vol.Required(ATTR_VALUE): vol.Coerce(int),
vol.Optional(ATTR_LABELS): cv.ensure_list(cv.string),
}
)
WINK_COMPONENTS = [
"binary_sensor",
"sensor",
"light",
"switch",
"lock",
"cover",
"climate",
"fan",
"alarm_control_panel",
"scene",
"water_heater",
]
WINK_HUBS: list[Any] = []
def _request_app_setup(hass, config):
"""Assist user with configuring the Wink dev application."""
hass.data[DOMAIN]["configurator"] = True
configurator = hass.components.configurator
def wink_configuration_callback(callback_data):
"""Handle configuration updates."""
_config_path = hass.config.path(WINK_CONFIG_FILE)
if not os.path.isfile(_config_path):
setup(hass, config)
return
client_id = callback_data.get(CONF_CLIENT_ID).strip()
client_secret = callback_data.get(CONF_CLIENT_SECRET).strip()
if None not in (client_id, client_secret):
save_json(
_config_path,
{CONF_CLIENT_ID: client_id, CONF_CLIENT_SECRET: client_secret},
)
setup(hass, config)
return
error_msg = "Your input was invalid. Please try again."
_configurator = hass.data[DOMAIN]["configuring"][DOMAIN]
configurator.notify_errors(_configurator, error_msg)
start_url = f"{get_url(hass)}{WINK_AUTH_CALLBACK_PATH}"
description = f"""Please create a Wink developer app at
https://developer.wink.com.
Add a Redirect URI of {start_url}.
They will provide you a Client ID and secret
after reviewing your request.
(This can take several days).
"""
hass.data[DOMAIN]["configuring"][DOMAIN] = configurator.request_config(
DOMAIN,
wink_configuration_callback,
description=description,
submit_caption="submit",
description_image="/static/images/config_wink.png",
fields=[
{"id": CONF_CLIENT_ID, "name": "Client ID", "type": "string"},
{"id": CONF_CLIENT_SECRET, "name": "Client secret", "type": "string"},
],
)
def _request_oauth_completion(hass, config):
"""Request user complete Wink OAuth2 flow."""
hass.data[DOMAIN]["configurator"] = True
configurator = hass.components.configurator
if DOMAIN in hass.data[DOMAIN]["configuring"]:
configurator.notify_errors(
hass.data[DOMAIN]["configuring"][DOMAIN],
"Failed to register, please try again.",
)
return
def wink_configuration_callback(callback_data):
"""Call setup again."""
setup(hass, config)
start_url = f"{get_url(hass)}{WINK_AUTH_START}"
description = f"Please authorize Wink by visiting {start_url}"
hass.data[DOMAIN]["configuring"][DOMAIN] = configurator.request_config(
DOMAIN, wink_configuration_callback, description=description
)
def setup(hass, config): # noqa: C901
"""Set up the Wink component."""
_LOGGER.warning(
"The Wink integration has been deprecated and is pending removal in "
"Home Assistant Core 2021.11"
)
if hass.data.get(DOMAIN) is None:
hass.data[DOMAIN] = {
"unique_ids": [],
"entities": {},
"oauth": {},
"configuring": {},
"pubnub": None,
"configurator": False,
}
if config.get(DOMAIN) is not None:
client_id = config[DOMAIN].get(CONF_CLIENT_ID)
client_secret = config[DOMAIN].get(CONF_CLIENT_SECRET)
email = config[DOMAIN].get(CONF_EMAIL)
password = config[DOMAIN].get(CONF_PASSWORD)
local_control = config[DOMAIN].get(CONF_LOCAL_CONTROL)
else:
client_id = None
client_secret = None
email = None
password = None
local_control = None
hass.data[DOMAIN]["configurator"] = True
if None not in [client_id, client_secret]:
_LOGGER.info("Using legacy OAuth authentication")
if not local_control:
pywink.disable_local_control()
hass.data[DOMAIN]["oauth"][CONF_CLIENT_ID] = client_id
hass.data[DOMAIN]["oauth"][CONF_CLIENT_SECRET] = client_secret
hass.data[DOMAIN]["oauth"]["email"] = email
hass.data[DOMAIN]["oauth"]["password"] = password
pywink.legacy_set_wink_credentials(email, password, client_id, client_secret)
else:
_LOGGER.info("Using OAuth authentication")
if not local_control:
pywink.disable_local_control()
config_path = hass.config.path(WINK_CONFIG_FILE)
if os.path.isfile(config_path):
config_file = load_json(config_path)
if config_file == DEFAULT_CONFIG:
_request_app_setup(hass, config)
return True
# else move on because the user modified the file
else:
save_json(config_path, DEFAULT_CONFIG)
_request_app_setup(hass, config)
return True
if DOMAIN in hass.data[DOMAIN]["configuring"]:
_configurator = hass.data[DOMAIN]["configuring"]
hass.components.configurator.request_done(_configurator.pop(DOMAIN))
# Using oauth
access_token = config_file.get(ATTR_ACCESS_TOKEN)
refresh_token = config_file.get(ATTR_REFRESH_TOKEN)
# This will be called after authorizing Home-Assistant
if None not in (access_token, refresh_token):
pywink.set_wink_credentials(
config_file.get(CONF_CLIENT_ID),
config_file.get(CONF_CLIENT_SECRET),
access_token=access_token,
refresh_token=refresh_token,
)
# This is called to create the redirect so the user can Authorize
# Home .
else:
redirect_uri = f"{get_url(hass)}{WINK_AUTH_CALLBACK_PATH}"
wink_auth_start_url = pywink.get_authorization_url(
config_file.get(CONF_CLIENT_ID), redirect_uri
)
hass.http.register_redirect(WINK_AUTH_START, wink_auth_start_url)
hass.http.register_view(
WinkAuthCallbackView(config, config_file, pywink.request_token)
)
_request_oauth_completion(hass, config)
return True
pywink.set_user_agent(USER_AGENT)
sub_details = pywink.get_subscription_details()
hass.data[DOMAIN]["pubnub"] = PubNubSubscriptionHandler(
sub_details[0], origin=sub_details[1]
)
def _subscribe():
hass.data[DOMAIN]["pubnub"].subscribe()
# Call subscribe after the user sets up wink via the configurator
# All other methods will complete setup before
# EVENT_HOMEASSISTANT_START is called meaning they
# will call subscribe via the method below. (start_subscription)
if hass.data[DOMAIN]["configurator"]:
_subscribe()
def keep_alive_call(event_time):
"""Call the Wink API endpoints to keep PubNub working."""
_LOGGER.info("Polling the Wink API to keep PubNub updates flowing")
pywink.set_user_agent(str(int(time.time())))
_temp_response = pywink.get_user()
_LOGGER.debug(str(json.dumps(_temp_response)))
time.sleep(1)
pywink.set_user_agent(USER_AGENT)
_temp_response = pywink.wink_api_fetch()
_LOGGER.debug("%s", _temp_response)
_temp_response = pywink.post_session()
_LOGGER.debug("%s", _temp_response)
# Call the Wink API every hour to keep PubNub updates flowing
track_time_interval(hass, keep_alive_call, timedelta(minutes=60))
def start_subscription(event):
"""Start the PubNub subscription."""
_subscribe()
hass.bus.listen(EVENT_HOMEASSISTANT_START, start_subscription)
def stop_subscription(event):
"""Stop the PubNub subscription."""
hass.data[DOMAIN]["pubnub"].unsubscribe()
hass.data[DOMAIN]["pubnub"] = None
hass.bus.listen(EVENT_HOMEASSISTANT_STOP, stop_subscription)
def save_credentials(event):
"""Save currently set OAuth credentials."""
if hass.data[DOMAIN]["oauth"].get("email") is None:
config_path = hass.config.path(WINK_CONFIG_FILE)
_config = pywink.get_current_oauth_credentials()
save_json(config_path, _config)
hass.bus.listen(EVENT_HOMEASSISTANT_STOP, save_credentials)
# Save the users potentially updated oauth credentials at a regular
# interval to prevent them from being expired after a HA reboot.
track_time_interval(hass, save_credentials, timedelta(minutes=60))
def force_update(call):
"""Force all devices to poll the Wink API."""
_LOGGER.info("Refreshing Wink states from API")
for entity_list in hass.data[DOMAIN]["entities"].values():
# Throttle the calls to Wink API
for entity in entity_list:
time.sleep(1)
entity.schedule_update_ha_state(True)
hass.services.register(DOMAIN, SERVICE_REFRESH_STATES, force_update)
def pull_new_devices(call):
"""Pull new devices added to users Wink account since startup."""
_LOGGER.info("Getting new devices from Wink API")
for _component in WINK_COMPONENTS:
discovery.load_platform(hass, _component, DOMAIN, {}, config)
hass.services.register(DOMAIN, SERVICE_ADD_NEW_DEVICES, pull_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, 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, 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,
schema=SET_PAIRING_MODE_SCHEMA,
)
def nimbus_service_handle(service):
"""Handle nimbus services."""
entity_id = service.data.get("entity_id")[0]
_all_dials = []
for sensor in hass.data[DOMAIN]["entities"]["sensor"]:
if isinstance(sensor, WinkNimbusDialDevice):
_all_dials.append(sensor)
for _dial in _all_dials:
if _dial.entity_id == entity_id:
if service.service == SERVICE_SET_DIAL_CONFIG:
_dial.set_configuration(**service.data)
if service.service == SERVICE_SET_DIAL_STATE:
_dial.wink.set_state(
service.data.get("value"), service.data.get("labels")
)
def siren_service_handle(service):
"""Handle siren services."""
entity_ids = service.data.get("entity_id")
all_sirens = []
for switch in hass.data[DOMAIN]["entities"]["switch"]:
if isinstance(switch, WinkSirenDevice):
all_sirens.append(switch)
sirens_to_set = []
if entity_ids is None:
sirens_to_set = all_sirens
else:
for siren in all_sirens:
if siren.entity_id in entity_ids:
sirens_to_set.append(siren)
for siren in sirens_to_set:
_man = siren.wink.device_manufacturer()
if (
service.service != SERVICE_SET_AUTO_SHUTOFF
and service.service != SERVICE_ENABLE_SIREN
and _man not in ("dome", "wink")
):
_LOGGER.error("Service only valid for Dome or Wink sirens")
return
if service.service == SERVICE_ENABLE_SIREN:
siren.wink.set_state(service.data.get(ATTR_ENABLED))
elif service.service == SERVICE_SET_AUTO_SHUTOFF:
siren.wink.set_auto_shutoff(service.data.get(ATTR_AUTO_SHUTOFF))
elif service.service == SERVICE_SET_CHIME_VOLUME:
siren.wink.set_chime_volume(service.data.get(ATTR_VOLUME))
elif service.service == SERVICE_SET_SIREN_VOLUME:
siren.wink.set_siren_volume(service.data.get(ATTR_VOLUME))
elif service.service == SERVICE_SET_SIREN_TONE:
siren.wink.set_siren_sound(service.data.get(ATTR_TONE))
elif service.service == SERVICE_ENABLE_CHIME:
siren.wink.set_chime(service.data.get(ATTR_TONE))
elif service.service == SERVICE_SIREN_STROBE_ENABLED:
siren.wink.set_siren_strobe_enabled(service.data.get(ATTR_ENABLED))
elif service.service == SERVICE_CHIME_STROBE_ENABLED:
siren.wink.set_chime_strobe_enabled(service.data.get(ATTR_ENABLED))
# Load components for the devices in Wink that we support
for wink_component in WINK_COMPONENTS:
hass.data[DOMAIN]["entities"][wink_component] = []
discovery.load_platform(hass, wink_component, DOMAIN, {}, config)
component = EntityComponent(_LOGGER, DOMAIN, hass)
sirens = []
has_dome_or_wink_siren = False
for siren in pywink.get_sirens():
_man = siren.device_manufacturer()
if _man in ("dome", "wink"):
has_dome_or_wink_siren = True
_id = siren.object_id() + siren.name()
if _id not in hass.data[DOMAIN]["unique_ids"]:
sirens.append(WinkSirenDevice(siren, hass))
if sirens:
hass.services.register(
DOMAIN,
SERVICE_SET_AUTO_SHUTOFF,
siren_service_handle,
schema=SET_AUTO_SHUTOFF_SCHEMA,
)
hass.services.register(
DOMAIN,
SERVICE_ENABLE_SIREN,
siren_service_handle,
schema=ENABLED_SIREN_SCHEMA,
)
if has_dome_or_wink_siren:
hass.services.register(
DOMAIN,
SERVICE_SET_SIREN_TONE,
siren_service_handle,
schema=SET_SIREN_TONE_SCHEMA,
)
hass.services.register(
DOMAIN,
SERVICE_ENABLE_CHIME,
siren_service_handle,
schema=SET_CHIME_MODE_SCHEMA,
)
hass.services.register(
DOMAIN,
SERVICE_SET_SIREN_VOLUME,
siren_service_handle,
schema=SET_VOLUME_SCHEMA,
)
hass.services.register(
DOMAIN,
SERVICE_SET_CHIME_VOLUME,
siren_service_handle,
schema=SET_VOLUME_SCHEMA,
)
hass.services.register(
DOMAIN,
SERVICE_SIREN_STROBE_ENABLED,
siren_service_handle,
schema=SET_STROBE_ENABLED_SCHEMA,
)
hass.services.register(
DOMAIN,
SERVICE_CHIME_STROBE_ENABLED,
siren_service_handle,
schema=SET_STROBE_ENABLED_SCHEMA,
)
component.add_entities(sirens)
nimbi = []
dials = {}
all_nimbi = pywink.get_cloud_clocks()
all_dials = []
for nimbus in all_nimbi:
if nimbus.object_type() == "cloud_clock":
nimbi.append(nimbus)
dials[nimbus.object_id()] = []
for nimbus in all_nimbi:
if nimbus.object_type() == "dial":
dials[nimbus.parent_id()].append(nimbus)
for nimbus in nimbi:
for dial in dials[nimbus.object_id()]:
all_dials.append(WinkNimbusDialDevice(nimbus, dial, hass))
if nimbi:
hass.services.register(
DOMAIN,
SERVICE_SET_DIAL_CONFIG,
nimbus_service_handle,
schema=DIAL_CONFIG_SCHEMA,
)
hass.services.register(
DOMAIN,
SERVICE_SET_DIAL_STATE,
nimbus_service_handle,
schema=DIAL_STATE_SCHEMA,
)
component.add_entities(all_dials)
return True
class WinkAuthCallbackView(HomeAssistantView):
"""Handle OAuth finish callback requests."""
url = "/auth/wink/callback"
name = "auth:wink:callback"
requires_auth = False
def __init__(self, config, config_file, request_token):
"""Initialize the OAuth callback view."""
self.config = config
self.config_file = config_file
self.request_token = request_token
@callback
def get(self, request):
"""Finish OAuth callback request."""
hass = request.app["hass"]
data = request.query
response_message = """Wink has been successfully authorized!
You can close this window now! For the best results you should reboot
Home Assistant"""
html_response = """<html><head><title>Wink Auth</title></head>
<body><h1>{}</h1></body></html>"""
if data.get("code") is not None:
response = self.request_token(
data.get("code"), self.config_file[CONF_CLIENT_SECRET]
)
config_contents = {
ATTR_ACCESS_TOKEN: response["access_token"],
ATTR_REFRESH_TOKEN: response["refresh_token"],
CONF_CLIENT_ID: self.config_file[CONF_CLIENT_ID],
CONF_CLIENT_SECRET: self.config_file[CONF_CLIENT_SECRET],
}
save_json(hass.config.path(WINK_CONFIG_FILE), config_contents)
hass.async_add_job(setup, hass, self.config)
return Response(
text=html_response.format(response_message), content_type="text/html"
)
error_msg = "No code returned from Wink API"
_LOGGER.error(error_msg)
return Response(text=html_response.format(error_msg), content_type="text/html")
class WinkDevice(Entity):
"""Representation a base Wink device."""
def __init__(self, wink, hass):
"""Initialize the Wink device."""
self.hass = hass
self.wink = wink
hass.data[DOMAIN]["pubnub"].add_subscription(
self.wink.pubnub_channel, self._pubnub_update
)
hass.data[DOMAIN]["unique_ids"].append(self.wink.object_id() + self.wink.name())
def _pubnub_update(self, message):
_LOGGER.debug(message)
try:
if message is None:
_LOGGER.error(
"Error on pubnub update for %s polling API for current state",
self.name,
)
self.schedule_update_ha_state(True)
else:
self.wink.pubnub_update(message)
self.schedule_update_ha_state()
except (ValueError, KeyError, AttributeError):
_LOGGER.error(
"Error in pubnub JSON for %s polling API for current state", self.name
)
self.schedule_update_ha_state(True)
@property
def name(self):
"""Return the name of the device."""
return self.wink.name()
@property
def unique_id(self):
"""Return the unique id of the Wink device."""
if hasattr(self.wink, "capability") and self.wink.capability() is not None:
return f"{self.wink.object_id()}_{self.wink.capability()}"
return self.wink.object_id()
@property
def available(self):
"""Return true if connection == True."""
return self.wink.available()
def update(self):
"""Update state of the device."""
self.wink.update_state()
@property
def should_poll(self):
"""Only poll if we are not subscribed to pubnub."""
return self.wink.pubnub_channel is None
@property
def extra_state_attributes(self):
"""Return the state attributes."""
attributes = {}
battery = self._battery_level
if battery:
attributes[ATTR_BATTERY_LEVEL] = battery
man_dev_model = self._manufacturer_device_model
if man_dev_model:
attributes["manufacturer_device_model"] = man_dev_model
man_dev_id = self._manufacturer_device_id
if man_dev_id:
attributes["manufacturer_device_id"] = man_dev_id
dev_man = self._device_manufacturer
if dev_man:
attributes["device_manufacturer"] = dev_man
model_name = self._model_name
if model_name:
attributes["model_name"] = model_name
tamper = self._tamper
if tamper is not None:
attributes["tamper_detected"] = tamper
return attributes
@property
def _battery_level(self):
"""Return the battery level."""
if self.wink.battery_level() is not None:
return self.wink.battery_level() * 100
@property
def _manufacturer_device_model(self):
"""Return the manufacturer device model."""
return self.wink.manufacturer_device_model()
@property
def _manufacturer_device_id(self):
"""Return the manufacturer device id."""
return self.wink.manufacturer_device_id()
@property
def _device_manufacturer(self):
"""Return the device manufacturer."""
return self.wink.device_manufacturer()
@property
def _model_name(self):
"""Return the model name."""
return self.wink.model_name()
@property
def _tamper(self):
"""Return the devices tamper status."""
if hasattr(self.wink, "tamper_detected"):
return self.wink.tamper_detected()
return None
class WinkSirenDevice(WinkDevice):
"""Representation of a Wink siren device."""
async def async_added_to_hass(self):
"""Call when entity is added to hass."""
self.hass.data[DOMAIN]["entities"]["switch"].append(self)
@property
def state(self):
"""Return sirens state."""
if self.wink.state():
return STATE_ON
return STATE_OFF
@property
def icon(self):
"""Return the icon to use in the frontend, if any."""
return "mdi:bell-ring"
@property
def extra_state_attributes(self):
"""Return the device state attributes."""
attributes = super().extra_state_attributes
auto_shutoff = self.wink.auto_shutoff()
if auto_shutoff is not None:
attributes["auto_shutoff"] = auto_shutoff
siren_volume = self.wink.siren_volume()
if siren_volume is not None:
attributes["siren_volume"] = siren_volume
chime_volume = self.wink.chime_volume()
if chime_volume is not None:
attributes["chime_volume"] = chime_volume
strobe_enabled = self.wink.strobe_enabled()
if strobe_enabled is not None:
attributes["siren_strobe_enabled"] = strobe_enabled
chime_strobe_enabled = self.wink.chime_strobe_enabled()
if chime_strobe_enabled is not None:
attributes["chime_strobe_enabled"] = chime_strobe_enabled
siren_sound = self.wink.siren_sound()
if siren_sound is not None:
attributes["siren_sound"] = siren_sound
chime_mode = self.wink.chime_mode()
if chime_mode is not None:
attributes["chime_mode"] = chime_mode
return attributes
class WinkNimbusDialDevice(WinkDevice):
"""Representation of the Quirky Nimbus device."""
def __init__(self, nimbus, dial, hass):
"""Initialize the Nimbus dial."""
super().__init__(dial, hass)
self.parent = nimbus
async def async_added_to_hass(self):
"""Call when entity is added to hass."""
self.hass.data[DOMAIN]["entities"]["sensor"].append(self)
@property
def state(self):
"""Return dials current value."""
return self.wink.state()
@property
def name(self):
"""Return the name of the device."""
return f"{self.parent.name()} dial {self.wink.index() + 1}"
@property
def extra_state_attributes(self):
"""Return the device state attributes."""
attributes = super().extra_state_attributes
dial_attributes = self.dial_attributes()
return {**attributes, **dial_attributes}
def dial_attributes(self):
"""Return the dial only attributes."""
return {
"labels": self.wink.labels(),
"position": self.wink.position(),
"rotation": self.wink.rotation(),
"max_value": self.wink.max_value(),
"min_value": self.wink.min_value(),
"num_ticks": self.wink.ticks(),
"scale_type": self.wink.scale(),
"max_position": self.wink.max_position(),
"min_position": self.wink.min_position(),
}
def set_configuration(self, **kwargs):
"""
Set the dial config.
Anything not sent will default to current setting.
"""
attributes = {**self.dial_attributes(), **kwargs}
min_value = attributes["min_value"]
max_value = attributes["max_value"]
rotation = attributes["rotation"]
ticks = attributes["num_ticks"]
scale = attributes["scale_type"]
min_position = attributes["min_position"]
max_position = attributes["max_position"]
self.wink.set_configuration(
min_value,
max_value,
rotation,
scale=scale,
ticks=ticks,
min_position=min_position,
max_position=max_position,
)

View File

@ -1,75 +0,0 @@
"""Support Wink alarm control panels."""
import pywink
import homeassistant.components.alarm_control_panel as alarm
from homeassistant.components.alarm_control_panel.const import (
SUPPORT_ALARM_ARM_AWAY,
SUPPORT_ALARM_ARM_HOME,
)
from homeassistant.const import (
STATE_ALARM_ARMED_AWAY,
STATE_ALARM_ARMED_HOME,
STATE_ALARM_DISARMED,
)
from . import DOMAIN, WinkDevice
STATE_ALARM_PRIVACY = "Private"
def setup_platform(hass, config, add_entities, discovery_info=None):
"""Set up the Wink platform."""
for camera in pywink.get_cameras():
# get_cameras returns multiple device types.
# Only add those that aren't sensors.
try:
camera.capability()
except AttributeError:
_id = camera.object_id() + camera.name()
if _id not in hass.data[DOMAIN]["unique_ids"]:
add_entities([WinkCameraDevice(camera, hass)])
class WinkCameraDevice(WinkDevice, alarm.AlarmControlPanelEntity):
"""Representation a Wink camera alarm."""
async def async_added_to_hass(self):
"""Call when entity is added to hass."""
self.hass.data[DOMAIN]["entities"]["alarm_control_panel"].append(self)
@property
def state(self):
"""Return the state of the device."""
wink_state = self.wink.state()
if wink_state == "away":
state = STATE_ALARM_ARMED_AWAY
elif wink_state == "home":
state = STATE_ALARM_DISARMED
elif wink_state == "night":
state = STATE_ALARM_ARMED_HOME
else:
state = None
return state
@property
def supported_features(self) -> int:
"""Return the list of supported features."""
return SUPPORT_ALARM_ARM_HOME | SUPPORT_ALARM_ARM_AWAY
def alarm_disarm(self, code=None):
"""Send disarm command."""
self.wink.set_mode("home")
def alarm_arm_home(self, code=None):
"""Send arm home command."""
self.wink.set_mode("night")
def alarm_arm_away(self, code=None):
"""Send arm away command."""
self.wink.set_mode("away")
@property
def extra_state_attributes(self):
"""Return the state attributes."""
return {"private": self.wink.private()}

View File

@ -1,197 +0,0 @@
"""Support for Wink binary sensors."""
import logging
import pywink
from homeassistant.components.binary_sensor import (
DEVICE_CLASS_MOISTURE,
DEVICE_CLASS_MOTION,
DEVICE_CLASS_OCCUPANCY,
DEVICE_CLASS_OPENING,
DEVICE_CLASS_SMOKE,
DEVICE_CLASS_SOUND,
DEVICE_CLASS_VIBRATION,
BinarySensorEntity,
)
from . import DOMAIN, WinkDevice
_LOGGER = logging.getLogger(__name__)
# These are the available sensors mapped to binary_sensor class
SENSOR_TYPES = {
"brightness": "light",
"capturing_audio": DEVICE_CLASS_SOUND,
"capturing_video": None,
"co_detected": "gas",
"liquid_detected": DEVICE_CLASS_MOISTURE,
"loudness": DEVICE_CLASS_SOUND,
"motion": DEVICE_CLASS_MOTION,
"noise": DEVICE_CLASS_SOUND,
"opened": DEVICE_CLASS_OPENING,
"presence": DEVICE_CLASS_OCCUPANCY,
"smoke_detected": DEVICE_CLASS_SMOKE,
"vibration": DEVICE_CLASS_VIBRATION,
}
def setup_platform(hass, config, add_entities, discovery_info=None):
"""Set up the Wink binary sensor platform."""
for sensor in pywink.get_sensors():
_id = sensor.object_id() + sensor.name()
if (
_id not in hass.data[DOMAIN]["unique_ids"]
and sensor.capability() in SENSOR_TYPES
):
add_entities([WinkBinarySensorEntity(sensor, hass)])
for key in pywink.get_keys():
_id = key.object_id() + key.name()
if _id not in hass.data[DOMAIN]["unique_ids"]:
add_entities([WinkBinarySensorEntity(key, hass)])
for sensor in pywink.get_smoke_and_co_detectors():
_id = sensor.object_id() + sensor.name()
if _id not in hass.data[DOMAIN]["unique_ids"]:
add_entities([WinkSmokeDetector(sensor, hass)])
for hub in pywink.get_hubs():
_id = hub.object_id() + hub.name()
if _id not in hass.data[DOMAIN]["unique_ids"]:
add_entities([WinkHub(hub, hass)])
for remote in pywink.get_remotes():
_id = remote.object_id() + remote.name()
if _id not in hass.data[DOMAIN]["unique_ids"]:
add_entities([WinkRemote(remote, hass)])
for button in pywink.get_buttons():
_id = button.object_id() + button.name()
if _id not in hass.data[DOMAIN]["unique_ids"]:
add_entities([WinkButton(button, hass)])
for gang in pywink.get_gangs():
_id = gang.object_id() + gang.name()
if _id not in hass.data[DOMAIN]["unique_ids"]:
add_entities([WinkGang(gang, hass)])
for door_bell_sensor in pywink.get_door_bells():
_id = door_bell_sensor.object_id() + door_bell_sensor.name()
if _id not in hass.data[DOMAIN]["unique_ids"]:
add_entities([WinkBinarySensorEntity(door_bell_sensor, hass)])
for camera_sensor in pywink.get_cameras():
_id = camera_sensor.object_id() + camera_sensor.name()
if _id not in hass.data[DOMAIN]["unique_ids"]:
try:
if camera_sensor.capability() in SENSOR_TYPES:
add_entities([WinkBinarySensorEntity(camera_sensor, hass)])
except AttributeError:
_LOGGER.info("Device isn't a sensor, skipping")
class WinkBinarySensorEntity(WinkDevice, BinarySensorEntity):
"""Representation of a Wink binary sensor."""
def __init__(self, wink, hass):
"""Initialize the Wink binary sensor."""
super().__init__(wink, hass)
if hasattr(self.wink, "unit"):
self._unit_of_measurement = self.wink.unit()
else:
self._unit_of_measurement = None
if hasattr(self.wink, "capability"):
self.capability = self.wink.capability()
else:
self.capability = None
async def async_added_to_hass(self):
"""Call when entity is added to hass."""
self.hass.data[DOMAIN]["entities"]["binary_sensor"].append(self)
@property
def is_on(self):
"""Return true if the binary sensor is on."""
return self.wink.state()
@property
def device_class(self):
"""Return the class of this sensor, from DEVICE_CLASSES."""
return SENSOR_TYPES.get(self.capability)
@property
def extra_state_attributes(self):
"""Return the device state attributes."""
return super().extra_state_attributes
class WinkSmokeDetector(WinkBinarySensorEntity):
"""Representation of a Wink Smoke detector."""
@property
def extra_state_attributes(self):
"""Return the device state attributes."""
_attributes = super().extra_state_attributes
_attributes["test_activated"] = self.wink.test_activated()
return _attributes
class WinkHub(WinkBinarySensorEntity):
"""Representation of a Wink Hub."""
@property
def extra_state_attributes(self):
"""Return the device state attributes."""
_attributes = super().extra_state_attributes
_attributes["update_needed"] = self.wink.update_needed()
_attributes["firmware_version"] = self.wink.firmware_version()
_attributes["pairing_mode"] = self.wink.pairing_mode()
_kidde_code = self.wink.kidde_radio_code()
if _kidde_code is not None:
# The service call to set the Kidde code
# takes a string of 1s and 0s so it makes
# sense to display it to the user that way
_formatted_kidde_code = f"{_kidde_code:b}".zfill(8)
_attributes["kidde_radio_code"] = _formatted_kidde_code
return _attributes
class WinkRemote(WinkBinarySensorEntity):
"""Representation of a Wink Lutron Connected bulb remote."""
@property
def extra_state_attributes(self):
"""Return the state attributes."""
_attributes = super().extra_state_attributes
_attributes["button_on_pressed"] = self.wink.button_on_pressed()
_attributes["button_off_pressed"] = self.wink.button_off_pressed()
_attributes["button_up_pressed"] = self.wink.button_up_pressed()
_attributes["button_down_pressed"] = self.wink.button_down_pressed()
return _attributes
@property
def device_class(self):
"""Return the class of this sensor, from DEVICE_CLASSES."""
return None
class WinkButton(WinkBinarySensorEntity):
"""Representation of a Wink Relay button."""
@property
def extra_state_attributes(self):
"""Return the device state attributes."""
_attributes = super().extra_state_attributes
_attributes["pressed"] = self.wink.pressed()
_attributes["long_pressed"] = self.wink.long_pressed()
return _attributes
class WinkGang(WinkBinarySensorEntity):
"""Representation of a Wink Relay gang."""
@property
def is_on(self):
"""Return true if the gang is connected."""
return self.wink.state()

View File

@ -1,520 +0,0 @@
"""Support for Wink thermostats and Air Conditioners."""
import logging
import pywink
from homeassistant.components.climate import ClimateEntity
from homeassistant.components.climate.const import (
ATTR_TARGET_TEMP_HIGH,
ATTR_TARGET_TEMP_LOW,
CURRENT_HVAC_COOL,
CURRENT_HVAC_HEAT,
CURRENT_HVAC_IDLE,
CURRENT_HVAC_OFF,
FAN_AUTO,
FAN_HIGH,
FAN_LOW,
FAN_MEDIUM,
FAN_ON,
HVAC_MODE_AUTO,
HVAC_MODE_COOL,
HVAC_MODE_FAN_ONLY,
HVAC_MODE_HEAT,
HVAC_MODE_OFF,
PRESET_AWAY,
PRESET_ECO,
PRESET_NONE,
SUPPORT_AUX_HEAT,
SUPPORT_FAN_MODE,
SUPPORT_PRESET_MODE,
SUPPORT_TARGET_TEMPERATURE,
SUPPORT_TARGET_TEMPERATURE_RANGE,
)
from homeassistant.const import ATTR_TEMPERATURE, PRECISION_TENTHS, TEMP_CELSIUS
from homeassistant.helpers.temperature import display_temp as show_temp
from . import DOMAIN, WinkDevice
_LOGGER = logging.getLogger(__name__)
ATTR_ECO_TARGET = "eco_target"
ATTR_EXTERNAL_TEMPERATURE = "external_temperature"
ATTR_OCCUPIED = "occupied"
ATTR_SCHEDULE_ENABLED = "schedule_enabled"
ATTR_SMART_TEMPERATURE = "smart_temperature"
ATTR_TOTAL_CONSUMPTION = "total_consumption"
HA_HVAC_TO_WINK = {
HVAC_MODE_AUTO: "auto",
HVAC_MODE_COOL: "cool_only",
HVAC_MODE_FAN_ONLY: "fan_only",
HVAC_MODE_HEAT: "heat_only",
HVAC_MODE_OFF: "off",
}
WINK_HVAC_TO_HA = {value: key for key, value in HA_HVAC_TO_WINK.items()}
SUPPORT_FLAGS_THERMOSTAT = (
SUPPORT_TARGET_TEMPERATURE
| SUPPORT_TARGET_TEMPERATURE_RANGE
| SUPPORT_FAN_MODE
| SUPPORT_AUX_HEAT
)
SUPPORT_FAN_THERMOSTAT = [FAN_AUTO, FAN_ON]
SUPPORT_PRESET_THERMOSTAT = [PRESET_AWAY, PRESET_ECO]
SUPPORT_FLAGS_AC = SUPPORT_TARGET_TEMPERATURE | SUPPORT_FAN_MODE | SUPPORT_PRESET_MODE
SUPPORT_FAN_AC = [FAN_HIGH, FAN_LOW, FAN_MEDIUM]
SUPPORT_PRESET_AC = [PRESET_NONE, PRESET_ECO]
def setup_platform(hass, config, add_entities, discovery_info=None):
"""Set up the Wink climate devices."""
for climate in pywink.get_thermostats():
_id = climate.object_id() + climate.name()
if _id not in hass.data[DOMAIN]["unique_ids"]:
add_entities([WinkThermostat(climate, hass)])
for climate in pywink.get_air_conditioners():
_id = climate.object_id() + climate.name()
if _id not in hass.data[DOMAIN]["unique_ids"]:
add_entities([WinkAC(climate, hass)])
class WinkThermostat(WinkDevice, ClimateEntity):
"""Representation of a Wink thermostat."""
@property
def supported_features(self):
"""Return the list of supported features."""
return SUPPORT_FLAGS_THERMOSTAT
async def async_added_to_hass(self):
"""Call when entity is added to hass."""
self.hass.data[DOMAIN]["entities"]["climate"].append(self)
@property
def temperature_unit(self):
"""Return the unit of measurement."""
# The Wink API always returns temp in Celsius
return TEMP_CELSIUS
@property
def extra_state_attributes(self):
"""Return the optional device state attributes."""
data = {}
if self.external_temperature is not None:
data[ATTR_EXTERNAL_TEMPERATURE] = show_temp(
self.hass,
self.external_temperature,
self.temperature_unit,
PRECISION_TENTHS,
)
if self.smart_temperature:
data[ATTR_SMART_TEMPERATURE] = self.smart_temperature
if self.occupied is not None:
data[ATTR_OCCUPIED] = self.occupied
if self.eco_target is not None:
data[ATTR_ECO_TARGET] = self.eco_target
return data
@property
def current_temperature(self):
"""Return the current temperature."""
return self.wink.current_temperature()
@property
def current_humidity(self):
"""Return the current humidity."""
if self.wink.current_humidity() is not None:
# The API states humidity will be a float 0-1
# the only example API response with humidity listed show an int
# This will address both possibilities
if self.wink.current_humidity() < 1:
return self.wink.current_humidity() * 100
return self.wink.current_humidity()
return None
@property
def external_temperature(self):
"""Return the current external temperature."""
return self.wink.current_external_temperature()
@property
def smart_temperature(self):
"""Return the current average temp of all remote sensor."""
return self.wink.current_smart_temperature()
@property
def eco_target(self):
"""Return status of eco target (Is the thermostat in eco mode)."""
return self.wink.eco_target()
@property
def occupied(self):
"""Return status of if the thermostat has detected occupancy."""
return self.wink.occupied()
@property
def preset_mode(self):
"""Return the current preset mode, e.g., home, away, temp."""
mode = self.wink.current_hvac_mode()
if mode == "eco":
return PRESET_ECO
if self.wink.away():
return PRESET_AWAY
return None
@property
def preset_modes(self):
"""Return a list of available preset modes."""
return SUPPORT_PRESET_THERMOSTAT
@property
def target_humidity(self):
"""Return the humidity we try to reach."""
target_hum = None
if self.wink.current_humidifier_mode() == "on":
if self.wink.current_humidifier_set_point() is not None:
target_hum = self.wink.current_humidifier_set_point() * 100
elif self.wink.current_dehumidifier_mode() == "on":
if self.wink.current_dehumidifier_set_point() is not None:
target_hum = self.wink.current_dehumidifier_set_point() * 100
else:
target_hum = None
return target_hum
@property
def target_temperature(self):
"""Return the temperature we try to reach."""
if self.hvac_mode != HVAC_MODE_AUTO and not self.wink.away():
if self.hvac_mode == HVAC_MODE_COOL:
return self.wink.current_max_set_point()
if self.hvac_mode == HVAC_MODE_HEAT:
return self.wink.current_min_set_point()
return None
@property
def target_temperature_low(self):
"""Return the lower bound temperature we try to reach."""
if self.hvac_mode == HVAC_MODE_AUTO:
return self.wink.current_min_set_point()
return None
@property
def target_temperature_high(self):
"""Return the higher bound temperature we try to reach."""
if self.hvac_mode == HVAC_MODE_AUTO:
return self.wink.current_max_set_point()
return None
@property
def is_aux_heat(self):
"""Return true if aux heater."""
if "aux" not in self.wink.hvac_modes():
return None
if self.wink.current_hvac_mode() == "aux":
return True
return False
@property
def hvac_mode(self) -> str:
"""Return hvac operation ie. heat, cool mode.
Need to be one of HVAC_MODE_*.
"""
if not self.wink.is_on():
return HVAC_MODE_OFF
wink_mode = self.wink.current_hvac_mode()
if wink_mode == "aux":
return HVAC_MODE_HEAT
if wink_mode == "eco":
return HVAC_MODE_AUTO
return WINK_HVAC_TO_HA.get(wink_mode, "")
@property
def hvac_modes(self):
"""Return the list of available hvac operation modes.
Need to be a subset of HVAC_MODES.
"""
hvac_list = [HVAC_MODE_OFF]
modes = self.wink.hvac_modes()
for mode in modes:
if mode in ("eco", "aux"):
continue
try:
ha_mode = WINK_HVAC_TO_HA[mode]
hvac_list.append(ha_mode)
except KeyError:
_LOGGER.error(
"Invalid operation mode mapping. %s doesn't map. "
"Please report this",
mode,
)
return hvac_list
@property
def hvac_action(self):
"""Return the current running hvac operation if supported.
Need to be one of CURRENT_HVAC_*.
"""
if not self.wink.is_on():
return CURRENT_HVAC_OFF
if self.wink.cool_on():
return CURRENT_HVAC_COOL
if self.wink.heat_on():
return CURRENT_HVAC_HEAT
return CURRENT_HVAC_IDLE
def set_temperature(self, **kwargs):
"""Set new target temperature."""
target_temp = kwargs.get(ATTR_TEMPERATURE)
target_temp_low = kwargs.get(ATTR_TARGET_TEMP_LOW)
target_temp_high = kwargs.get(ATTR_TARGET_TEMP_HIGH)
if target_temp is not None:
if self.hvac_mode == HVAC_MODE_COOL:
target_temp_high = target_temp
if self.hvac_mode == HVAC_MODE_HEAT:
target_temp_low = target_temp
self.wink.set_temperature(target_temp_low, target_temp_high)
def set_hvac_mode(self, hvac_mode):
"""Set new target hvac mode."""
hvac_mode_to_set = HA_HVAC_TO_WINK.get(hvac_mode)
self.wink.set_operation_mode(hvac_mode_to_set)
def set_preset_mode(self, preset_mode):
"""Set new preset mode."""
# Away
if preset_mode != PRESET_AWAY and self.wink.away():
self.wink.set_away_mode(False)
elif preset_mode == PRESET_AWAY:
self.wink.set_away_mode()
if preset_mode == PRESET_ECO:
self.wink.set_operation_mode("eco")
@property
def fan_mode(self):
"""Return whether the fan is on."""
if self.wink.current_fan_mode() == "on":
return FAN_ON
if self.wink.current_fan_mode() == "auto":
return FAN_AUTO
# No Fan available so disable slider
return None
@property
def fan_modes(self):
"""List of available fan modes."""
if self.wink.has_fan():
return SUPPORT_FAN_THERMOSTAT
return None
def set_fan_mode(self, fan_mode):
"""Turn fan on/off."""
self.wink.set_fan_mode(fan_mode.lower())
def turn_aux_heat_on(self):
"""Turn auxiliary heater on."""
self.wink.set_operation_mode("aux")
def turn_aux_heat_off(self):
"""Turn auxiliary heater off."""
self.wink.set_operation_mode("heat_only")
@property
def min_temp(self):
"""Return the minimum temperature."""
minimum = 7 # Default minimum
min_min = self.wink.min_min_set_point()
min_max = self.wink.min_max_set_point()
if self.hvac_mode == HVAC_MODE_HEAT:
if min_min:
return_value = min_min
else:
return_value = minimum
elif self.hvac_mode == HVAC_MODE_COOL:
if min_max:
return_value = min_max
else:
return_value = minimum
elif self.hvac_mode == HVAC_MODE_AUTO:
if min_min and min_max:
return_value = min(min_min, min_max)
else:
return_value = minimum
else:
return_value = minimum
return return_value
@property
def max_temp(self):
"""Return the maximum temperature."""
maximum = 35 # Default maximum
max_min = self.wink.max_min_set_point()
max_max = self.wink.max_max_set_point()
if self.hvac_mode == HVAC_MODE_HEAT:
if max_min:
return_value = max_min
else:
return_value = maximum
elif self.hvac_mode == HVAC_MODE_COOL:
if max_max:
return_value = max_max
else:
return_value = maximum
elif self.hvac_mode == HVAC_MODE_AUTO:
if max_min and max_max:
return_value = min(max_min, max_max)
else:
return_value = maximum
else:
return_value = maximum
return return_value
class WinkAC(WinkDevice, ClimateEntity):
"""Representation of a Wink air conditioner."""
@property
def supported_features(self):
"""Return the list of supported features."""
return SUPPORT_FLAGS_AC
@property
def temperature_unit(self):
"""Return the unit of measurement."""
# The Wink API always returns temp in Celsius
return TEMP_CELSIUS
@property
def extra_state_attributes(self):
"""Return the optional device state attributes."""
data = {}
data[ATTR_TOTAL_CONSUMPTION] = self.wink.total_consumption()
data[ATTR_SCHEDULE_ENABLED] = self.wink.schedule_enabled()
return data
@property
def current_temperature(self):
"""Return the current temperature."""
return self.wink.current_temperature()
@property
def preset_mode(self):
"""Return the current preset mode, e.g., home, away, temp."""
if not self.wink.is_on():
return PRESET_NONE
mode = self.wink.current_mode()
if mode == "auto_eco":
return PRESET_ECO
return PRESET_NONE
@property
def preset_modes(self):
"""Return a list of available preset modes."""
return SUPPORT_PRESET_AC
@property
def hvac_mode(self) -> str:
"""Return hvac operation ie. heat, cool mode.
Need to be one of HVAC_MODE_*.
"""
if not self.wink.is_on():
return HVAC_MODE_OFF
wink_mode = self.wink.current_mode()
if wink_mode == "auto_eco":
return HVAC_MODE_COOL
return WINK_HVAC_TO_HA.get(wink_mode, "")
@property
def hvac_modes(self):
"""Return the list of available hvac operation modes.
Need to be a subset of HVAC_MODES.
"""
hvac_list = [HVAC_MODE_OFF]
modes = self.wink.modes()
for mode in modes:
if mode == "auto_eco":
continue
try:
ha_mode = WINK_HVAC_TO_HA[mode]
hvac_list.append(ha_mode)
except KeyError:
_LOGGER.error(
"Invalid operation mode mapping. %s doesn't map. "
"Please report this",
mode,
)
return hvac_list
def set_temperature(self, **kwargs):
"""Set new target temperature."""
target_temp = kwargs.get(ATTR_TEMPERATURE)
self.wink.set_temperature(target_temp)
def set_hvac_mode(self, hvac_mode):
"""Set new target hvac mode."""
hvac_mode_to_set = HA_HVAC_TO_WINK.get(hvac_mode)
self.wink.set_operation_mode(hvac_mode_to_set)
def set_preset_mode(self, preset_mode):
"""Set new preset mode."""
if preset_mode == PRESET_ECO:
self.wink.set_operation_mode("auto_eco")
elif self.hvac_mode == HVAC_MODE_COOL and preset_mode == PRESET_NONE:
self.set_hvac_mode(HVAC_MODE_COOL)
@property
def target_temperature(self):
"""Return the temperature we try to reach."""
return self.wink.current_max_set_point()
@property
def fan_mode(self):
"""
Return the current fan mode.
The official Wink app only supports 3 modes [low, medium, high]
which are equal to [0.33, 0.66, 1.0] respectively.
"""
speed = self.wink.current_fan_speed()
if speed <= 0.33:
return FAN_LOW
if speed <= 0.66:
return FAN_MEDIUM
return FAN_HIGH
@property
def fan_modes(self):
"""Return a list of available fan modes."""
return SUPPORT_FAN_AC
def set_fan_mode(self, fan_mode):
"""
Set fan speed.
The official Wink app only supports 3 modes [low, medium, high]
which are equal to [0.33, 0.66, 1.0] respectively.
"""
if fan_mode == FAN_LOW:
speed = 0.33
elif fan_mode == FAN_MEDIUM:
speed = 0.66
elif fan_mode == FAN_HIGH:
speed = 1.0
self.wink.set_ac_fan_speed(speed)

View File

@ -1,57 +0,0 @@
"""Support for Wink covers."""
import pywink
from homeassistant.components.cover import ATTR_POSITION, CoverEntity
from . import DOMAIN, WinkDevice
def setup_platform(hass, config, add_entities, discovery_info=None):
"""Set up the Wink cover platform."""
for shade in pywink.get_shades():
_id = shade.object_id() + shade.name()
if _id not in hass.data[DOMAIN]["unique_ids"]:
add_entities([WinkCoverEntity(shade, hass)])
for shade in pywink.get_shade_groups():
_id = shade.object_id() + shade.name()
if _id not in hass.data[DOMAIN]["unique_ids"]:
add_entities([WinkCoverEntity(shade, hass)])
for door in pywink.get_garage_doors():
_id = door.object_id() + door.name()
if _id not in hass.data[DOMAIN]["unique_ids"]:
add_entities([WinkCoverEntity(door, hass)])
class WinkCoverEntity(WinkDevice, CoverEntity):
"""Representation of a Wink cover device."""
async def async_added_to_hass(self):
"""Call when entity is added to hass."""
self.hass.data[DOMAIN]["entities"]["cover"].append(self)
def close_cover(self, **kwargs):
"""Close the cover."""
self.wink.set_state(0)
def open_cover(self, **kwargs):
"""Open the cover."""
self.wink.set_state(1)
def set_cover_position(self, **kwargs):
"""Move the cover shutter to a specific position."""
position = kwargs.get(ATTR_POSITION)
self.wink.set_state(position / 100)
@property
def current_cover_position(self):
"""Return the current position of cover shutter."""
if self.wink.state() is not None:
return int(self.wink.state() * 100)
return None
@property
def is_closed(self):
"""Return if the cover is closed."""
state = self.wink.state()
return bool(state == 0)

View File

@ -1,112 +0,0 @@
"""Support for Wink fans."""
from __future__ import annotations
import pywink
from homeassistant.components.fan import (
SPEED_HIGH,
SPEED_LOW,
SPEED_MEDIUM,
SUPPORT_DIRECTION,
SUPPORT_SET_SPEED,
FanEntity,
)
from . import DOMAIN, WinkDevice
SPEED_AUTO = "auto"
SPEED_LOWEST = "lowest"
SUPPORTED_FEATURES = SUPPORT_DIRECTION + SUPPORT_SET_SPEED
def setup_platform(hass, config, add_entities, discovery_info=None):
"""Set up the Wink platform."""
for fan in pywink.get_fans():
if fan.object_id() + fan.name() not in hass.data[DOMAIN]["unique_ids"]:
add_entities([WinkFanDevice(fan, hass)])
class WinkFanDevice(WinkDevice, FanEntity):
"""Representation of a Wink fan."""
async def async_added_to_hass(self):
"""Call when entity is added to hass."""
self.hass.data[DOMAIN]["entities"]["fan"].append(self)
def set_direction(self, direction: str) -> None:
"""Set the direction of the fan."""
self.wink.set_fan_direction(direction)
def set_speed(self, speed: str) -> None:
"""Set the speed of the fan."""
self.wink.set_state(True, speed)
#
# The fan entity model has changed to use percentages and preset_modes
# instead of speeds.
#
# Please review
# https://developers.home-assistant.io/docs/core/entity/fan/
#
def turn_on(
self,
speed: str = None,
percentage: int = None,
preset_mode: str = None,
**kwargs,
) -> None:
"""Turn on the fan."""
self.wink.set_state(True, speed)
def turn_off(self, **kwargs) -> None:
"""Turn off the fan."""
self.wink.set_state(False)
@property
def is_on(self):
"""Return true if the entity is on."""
return self.wink.state()
@property
def speed(self) -> str | None:
"""Return the current speed."""
current_wink_speed = self.wink.current_fan_speed()
if SPEED_AUTO == current_wink_speed:
return SPEED_AUTO
if SPEED_LOWEST == current_wink_speed:
return SPEED_LOWEST
if SPEED_LOW == current_wink_speed:
return SPEED_LOW
if SPEED_MEDIUM == current_wink_speed:
return SPEED_MEDIUM
if SPEED_HIGH == current_wink_speed:
return SPEED_HIGH
return None
@property
def current_direction(self):
"""Return direction of the fan [forward, reverse]."""
return self.wink.current_fan_direction()
@property
def speed_list(self) -> list:
"""Get the list of available speeds."""
wink_supported_speeds = self.wink.fan_speeds()
supported_speeds = []
if SPEED_AUTO in wink_supported_speeds:
supported_speeds.append(SPEED_AUTO)
if SPEED_LOWEST in wink_supported_speeds:
supported_speeds.append(SPEED_LOWEST)
if SPEED_LOW in wink_supported_speeds:
supported_speeds.append(SPEED_LOW)
if SPEED_MEDIUM in wink_supported_speeds:
supported_speeds.append(SPEED_MEDIUM)
if SPEED_HIGH in wink_supported_speeds:
supported_speeds.append(SPEED_HIGH)
return supported_speeds
@property
def supported_features(self) -> int:
"""Flag supported features."""
return SUPPORTED_FEATURES

View File

@ -1,114 +0,0 @@
"""Support for Wink lights."""
import pywink
from homeassistant.components.light import (
ATTR_BRIGHTNESS,
ATTR_COLOR_TEMP,
ATTR_HS_COLOR,
SUPPORT_BRIGHTNESS,
SUPPORT_COLOR,
SUPPORT_COLOR_TEMP,
LightEntity,
)
from homeassistant.util import color as color_util
from homeassistant.util.color import (
color_temperature_mired_to_kelvin as mired_to_kelvin,
)
from . import DOMAIN, WinkDevice
def setup_platform(hass, config, add_entities, discovery_info=None):
"""Set up the Wink lights."""
for light in pywink.get_light_bulbs():
_id = light.object_id() + light.name()
if _id not in hass.data[DOMAIN]["unique_ids"]:
add_entities([WinkLight(light, hass)])
for light in pywink.get_light_groups():
_id = light.object_id() + light.name()
if _id not in hass.data[DOMAIN]["unique_ids"]:
add_entities([WinkLight(light, hass)])
class WinkLight(WinkDevice, LightEntity):
"""Representation of a Wink light."""
async def async_added_to_hass(self):
"""Call when entity is added to hass."""
self.hass.data[DOMAIN]["entities"]["light"].append(self)
@property
def is_on(self):
"""Return true if light is on."""
return self.wink.state()
@property
def brightness(self):
"""Return the brightness of the light."""
if self.wink.brightness() is not None:
return int(self.wink.brightness() * 255)
return None
@property
def hs_color(self):
"""Define current bulb color."""
if self.wink.supports_xy_color():
return color_util.color_xy_to_hs(*self.wink.color_xy())
if self.wink.supports_hue_saturation():
hue = self.wink.color_hue()
saturation = self.wink.color_saturation()
if hue is not None and saturation is not None:
return hue * 360, saturation * 100
return None
@property
def color_temp(self):
"""Define current bulb color in degrees Kelvin."""
if not self.wink.supports_temperature():
return None
return color_util.color_temperature_kelvin_to_mired(
self.wink.color_temperature_kelvin()
)
@property
def supported_features(self):
"""Flag supported features."""
supports = SUPPORT_BRIGHTNESS
if self.wink.supports_temperature():
supports = supports | SUPPORT_COLOR_TEMP
if self.wink.supports_xy_color():
supports = supports | SUPPORT_COLOR
elif self.wink.supports_hue_saturation():
supports = supports | SUPPORT_COLOR
return supports
def turn_on(self, **kwargs):
"""Turn the switch on."""
brightness = kwargs.get(ATTR_BRIGHTNESS)
hs_color = kwargs.get(ATTR_HS_COLOR)
color_temp_mired = kwargs.get(ATTR_COLOR_TEMP)
state_kwargs = {}
if hs_color:
if self.wink.supports_xy_color():
xy_color = color_util.color_hs_to_xy(*hs_color)
state_kwargs["color_xy"] = xy_color
if self.wink.supports_hue_saturation():
hs_scaled = hs_color[0] / 360, hs_color[1] / 100
state_kwargs["color_hue_saturation"] = hs_scaled
if color_temp_mired:
state_kwargs["color_kelvin"] = mired_to_kelvin(color_temp_mired)
if brightness:
state_kwargs["brightness"] = brightness / 255.0
self.wink.set_state(True, **state_kwargs)
def turn_off(self, **kwargs):
"""Turn the switch off."""
self.wink.set_state(False)

View File

@ -1,211 +0,0 @@
"""Support for Wink locks."""
import pywink
import voluptuous as vol
from homeassistant.components.lock import LockEntity
from homeassistant.const import (
ATTR_CODE,
ATTR_ENTITY_ID,
ATTR_MODE,
ATTR_NAME,
STATE_UNKNOWN,
)
import homeassistant.helpers.config_validation as cv
from . import DOMAIN, WinkDevice
SERVICE_SET_VACATION_MODE = "set_lock_vacation_mode"
SERVICE_SET_ALARM_MODE = "set_lock_alarm_mode"
SERVICE_SET_ALARM_SENSITIVITY = "set_lock_alarm_sensitivity"
SERVICE_SET_ALARM_STATE = "set_lock_alarm_state"
SERVICE_SET_BEEPER_STATE = "set_lock_beeper_state"
SERVICE_ADD_KEY = "add_new_lock_key_code"
ATTR_ENABLED = "enabled"
ATTR_SENSITIVITY = "sensitivity"
ALARM_SENSITIVITY_MAP = {
"low": 0.2,
"medium_low": 0.4,
"medium": 0.6,
"medium_high": 0.8,
"high": 1.0,
}
ALARM_MODES_MAP = {
"activity": "alert",
"forced_entry": "forced_entry",
"tamper": "tamper",
}
SET_ENABLED_SCHEMA = vol.Schema(
{vol.Optional(ATTR_ENTITY_ID): cv.entity_ids, vol.Required(ATTR_ENABLED): cv.string}
)
SET_SENSITIVITY_SCHEMA = vol.Schema(
{
vol.Optional(ATTR_ENTITY_ID): cv.entity_ids,
vol.Required(ATTR_SENSITIVITY): vol.In(ALARM_SENSITIVITY_MAP),
}
)
SET_ALARM_MODES_SCHEMA = vol.Schema(
{
vol.Optional(ATTR_ENTITY_ID): cv.entity_ids,
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_entities, discovery_info=None):
"""Set up the Wink platform."""
for lock in pywink.get_locks():
_id = lock.object_id() + lock.name()
if _id not in hass.data[DOMAIN]["unique_ids"]:
add_entities([WinkLockDevice(lock, hass)])
def service_handle(service):
"""Handle for services."""
entity_ids = service.data.get("entity_id")
all_locks = hass.data[DOMAIN]["entities"]["lock"]
locks_to_set = []
if entity_ids is None:
locks_to_set = all_locks
else:
for lock in all_locks:
if lock.entity_id in entity_ids:
locks_to_set.append(lock)
for lock in locks_to_set:
if service.service == SERVICE_SET_VACATION_MODE:
lock.set_vacation_mode(service.data.get(ATTR_ENABLED))
elif service.service == SERVICE_SET_ALARM_STATE:
lock.set_alarm_state(service.data.get(ATTR_ENABLED))
elif service.service == SERVICE_SET_BEEPER_STATE:
lock.set_beeper_state(service.data.get(ATTR_ENABLED))
elif service.service == SERVICE_SET_ALARM_MODE:
lock.set_alarm_mode(service.data.get(ATTR_MODE))
elif service.service == SERVICE_SET_ALARM_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)
hass.services.register(
DOMAIN, SERVICE_SET_VACATION_MODE, service_handle, schema=SET_ENABLED_SCHEMA
)
hass.services.register(
DOMAIN, SERVICE_SET_ALARM_STATE, service_handle, schema=SET_ENABLED_SCHEMA
)
hass.services.register(
DOMAIN, SERVICE_SET_BEEPER_STATE, service_handle, schema=SET_ENABLED_SCHEMA
)
hass.services.register(
DOMAIN, SERVICE_SET_ALARM_MODE, service_handle, schema=SET_ALARM_MODES_SCHEMA
)
hass.services.register(
DOMAIN,
SERVICE_SET_ALARM_SENSITIVITY,
service_handle,
schema=SET_SENSITIVITY_SCHEMA,
)
hass.services.register(
DOMAIN, SERVICE_ADD_KEY, service_handle, schema=ADD_KEY_SCHEMA
)
class WinkLockDevice(WinkDevice, LockEntity):
"""Representation of a Wink lock."""
async def async_added_to_hass(self):
"""Call when entity is added to hass."""
self.hass.data[DOMAIN]["entities"]["lock"].append(self)
@property
def is_locked(self):
"""Return true if device is locked."""
return self.wink.state()
def lock(self, **kwargs):
"""Lock the device."""
self.wink.set_state(True)
def unlock(self, **kwargs):
"""Unlock the device."""
self.wink.set_state(False)
def set_alarm_state(self, enabled):
"""Set lock's alarm state."""
self.wink.set_alarm_state(enabled)
def set_vacation_mode(self, enabled):
"""Set lock's vacation mode."""
self.wink.set_vacation_mode(enabled)
def set_beeper_state(self, enabled):
"""Set lock's beeper mode."""
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):
"""
Set lock's alarm sensitivity.
Valid sensitivities:
0.2, 0.4, 0.6, 0.8, 1.0
"""
self.wink.set_alarm_sensitivity(sensitivity)
def set_alarm_mode(self, mode):
"""
Set lock's alarm mode.
Valid modes:
alert - Beep when lock is locked or unlocked
tamper - 15 sec alarm when lock is disturbed when locked
forced_entry - 3 min alarm when significant force applied
to door when locked.
"""
self.wink.set_alarm_mode(mode)
@property
def extra_state_attributes(self):
"""Return the state attributes."""
super_attrs = super().extra_state_attributes
sensitivity = dict_value_to_key(
ALARM_SENSITIVITY_MAP, self.wink.alarm_sensitivity()
)
super_attrs["alarm_sensitivity"] = sensitivity
super_attrs["vacation_mode"] = self.wink.vacation_mode_enabled()
super_attrs["beeper_mode"] = self.wink.beeper_enabled()
super_attrs["auto_lock"] = self.wink.auto_lock_enabled()
alarm_mode = dict_value_to_key(ALARM_MODES_MAP, self.wink.alarm_mode())
super_attrs["alarm_mode"] = alarm_mode
super_attrs["alarm_enabled"] = self.wink.alarm_enabled()
return super_attrs
def dict_value_to_key(dict_map, comp_value):
"""Return the key that has the provided value."""
for key, value in dict_map.items():
if value == comp_value:
return key
return STATE_UNKNOWN

View File

@ -1,9 +0,0 @@
{
"domain": "wink",
"name": "Wink",
"documentation": "https://www.home-assistant.io/integrations/wink",
"requirements": ["pubnubsub-handler==1.0.9", "python-wink==1.10.5"],
"dependencies": ["configurator", "http"],
"codeowners": [],
"iot_class": "cloud_polling"
}

View File

@ -1,34 +0,0 @@
"""Support for Wink scenes."""
from typing import Any
import pywink
from homeassistant.components.scene import Scene
from . import DOMAIN, WinkDevice
def setup_platform(hass, config, add_entities, discovery_info=None):
"""Set up the Wink platform."""
for scene in pywink.get_scenes():
_id = scene.object_id() + scene.name()
if _id not in hass.data[DOMAIN]["unique_ids"]:
add_entities([WinkScene(scene, hass)])
class WinkScene(WinkDevice, Scene):
"""Representation of a Wink shortcut/scene."""
def __init__(self, wink, hass):
"""Initialize the Wink device."""
super().__init__(wink, hass)
hass.data[DOMAIN]["entities"]["scene"].append(self)
async def async_added_to_hass(self):
"""Call when entity is added to hass."""
self.hass.data[DOMAIN]["entities"]["scene"].append(self)
def activate(self, **kwargs: Any) -> None:
"""Activate the scene."""
self.wink.activate()

View File

@ -1,98 +0,0 @@
"""Support for Wink sensors."""
from contextlib import suppress
import logging
import pywink
from homeassistant.components.sensor import SensorEntity
from homeassistant.const import DEGREE, TEMP_CELSIUS
from . import DOMAIN, WinkDevice
_LOGGER = logging.getLogger(__name__)
SENSOR_TYPES = ["temperature", "humidity", "balance", "proximity"]
def setup_platform(hass, config, add_entities, discovery_info=None):
"""Set up the Wink platform."""
for sensor in pywink.get_sensors():
_id = sensor.object_id() + sensor.name()
if (
_id not in hass.data[DOMAIN]["unique_ids"]
and sensor.capability() in SENSOR_TYPES
):
add_entities([WinkSensorEntity(sensor, hass)])
for eggtray in pywink.get_eggtrays():
_id = eggtray.object_id() + eggtray.name()
if _id not in hass.data[DOMAIN]["unique_ids"]:
add_entities([WinkSensorEntity(eggtray, hass)])
for tank in pywink.get_propane_tanks():
_id = tank.object_id() + tank.name()
if _id not in hass.data[DOMAIN]["unique_ids"]:
add_entities([WinkSensorEntity(tank, hass)])
for piggy_bank in pywink.get_piggy_banks():
_id = piggy_bank.object_id() + piggy_bank.name()
if _id not in hass.data[DOMAIN]["unique_ids"]:
try:
if piggy_bank.capability() in SENSOR_TYPES:
add_entities([WinkSensorEntity(piggy_bank, hass)])
except AttributeError:
_LOGGER.info("Device is not a sensor")
class WinkSensorEntity(WinkDevice, SensorEntity):
"""Representation of a Wink sensor."""
def __init__(self, wink, hass):
"""Initialize the Wink device."""
super().__init__(wink, hass)
self.capability = self.wink.capability()
if self.wink.unit() == DEGREE:
self._unit_of_measurement = TEMP_CELSIUS
else:
self._unit_of_measurement = self.wink.unit()
async def async_added_to_hass(self):
"""Call when entity is added to hass."""
self.hass.data[DOMAIN]["entities"]["sensor"].append(self)
@property
def native_value(self):
"""Return the state."""
state = None
if self.capability == "humidity":
if self.wink.state() is not None:
state = round(self.wink.state())
elif self.capability == "temperature":
if self.wink.state() is not None:
state = round(self.wink.state(), 1)
elif self.capability == "balance":
if self.wink.state() is not None:
state = round(self.wink.state() / 100, 2)
elif self.capability == "proximity":
if self.wink.state() is not None:
state = self.wink.state()
else:
state = self.wink.state()
return state
@property
def native_unit_of_measurement(self):
"""Return the unit of measurement of this entity, if any."""
return self._unit_of_measurement
@property
def extra_state_attributes(self):
"""Return the state attributes."""
super_attrs = super().extra_state_attributes
# Ignore error, this sensor isn't an eggminder
with suppress(AttributeError):
super_attrs["egg_times"] = self.wink.eggs()
return super_attrs

View File

@ -1,431 +0,0 @@
# Describes the format for available Wink services
pair_new_device:
name: Pair new device
description: Pair a new device to a Wink Hub.
fields:
hub_name:
name: Hub name
description: The name of the hub to pair a new device to.
required: true
example: "My hub"
selector:
text:
pairing_mode:
name: Pairing mode
description: Mode.
required: true
selector:
select:
options:
- 'bluetooth'
- 'kidde'
- 'lutron'
- 'zigbee'
- 'zwave'
- 'zwave_exclusion'
- 'zwave_network_rediscovery'
kidde_radio_code:
name: Kidde radio code
description: "A string of 8 1s and 0s one for each dip switch on the kidde device left --> right = 1 --> 8. Down = 1 and Up = 0"
example: "10101010"
selector:
text:
rename_wink_device:
name: Rename wink device
description: Rename the provided device.
target:
entity:
integration: wink
fields:
name:
name: Name
description: The name to change it to.
required: true
example: back_door
selector:
text:
delete_wink_device:
name: Delete wink device
description: Remove/unpair device from Wink.
target:
entity:
integration: wink
pull_newly_added_devices_from_wink:
name: Pull newly added devices from wink
description: Pull newly paired devices from Wink.
refresh_state_from_wink:
name: Refresh state from wink
description: Pull the latest states for every device.
set_siren_volume:
name: Set siren volume
description: Set the volume of the siren for a Dome siren/chime.
target:
entity:
integration: wink
domain: switch
fields:
volume:
name: Volume
description: Volume level.
required: true
selector:
select:
options:
- 'low'
- 'medium'
- 'high'
enable_chime:
name: Enable chime
description: Enable the chime of a Dome siren with the provided sound.
target:
entity:
integration: wink
domain: switch
fields:
tone:
name: Tone
description: >-
The tone to use for the chime.
required: true
selector:
select:
options:
- 'alert'
- 'beep'
- 'beep_beep'
- 'doorbell'
- 'doorbell_extended'
- 'evacuation'
- 'fur_elise'
- 'inactive'
- 'police_siren'
- 'rondo_alla_turca'
- 'william_tell'
set_siren_tone:
name: Set siren tone
description: Set the sound to use when the siren is enabled. (This doesn't enable the siren)
target:
entity:
integration: wink
domain: switch
fields:
tone:
name: Tone
description: >-
The tone to use for the chime.
required: true
selector:
select:
options:
- 'alert'
- 'beep'
- 'beep_beep'
- 'doorbell'
- 'doorbell_extended'
- 'evacuation'
- 'fur_elise'
- 'inactive'
- 'police_siren'
- 'rondo_alla_turca'
- 'william_tell'
siren_set_auto_shutoff:
name: Siren set auto shutoff
description: How long to sound the siren before turning off.
target:
entity:
integration: wink
domain: switch
fields:
auto_shutoff:
name: Auto shutoff
description: >-
The time in seconds to sound the siren. (None and -1 are forever. Use None for gocontrol, and -1 for Dome)
required: true
selector:
select:
options:
- 'None'
- '-1'
- '30'
- '60'
- '120'
set_siren_strobe_enabled:
name: Set siren strobe enabled
description: Enable or disable the strobe light when the siren is sounding.
target:
entity:
integration: wink
domain: switch
fields:
enabled:
name: Enabled
description: "True or False"
required: true
selector:
boolean:
set_chime_strobe_enabled:
name: Set chime strobe enabled
description: Enable or disable the strobe light when the chime is sounding.
target:
entity:
integration: wink
domain: switch
fields:
enabled:
name: Enabled
description: "True or False"
required: true
selector:
boolean:
enable_siren:
name: Enable siren
description: Enable/disable the siren.
target:
entity:
integration: wink
domain: switch
fields:
enabled:
name: Enabled
description: "true or false"
required: true
selector:
boolean:
set_chime_volume:
name: Set chime volume
description: Set the volume of the chime for a Dome siren/chime.
target:
entity:
integration: wink
domain: switch
fields:
volume:
name: Volume
description: Volume level.
required: true
selector:
select:
options:
- 'low'
- 'medium'
- 'high'
set_nimbus_dial_configuration:
name: Set nimbus dial configuration
description: Set the configuration of an individual nimbus dial
target:
entity:
integration: wink
domain: switch
fields:
rotation:
name: Rotation
description: Direction dial hand should spin.
selector:
select:
options:
- 'cw'
- 'ccw'
ticks:
name: Ticks
description: Number of times the hand should move
selector:
number:
min: 0
max: 3600
scale:
name: Scale
description: How the dial should move in response to higher values.
selector:
select:
options:
- 'linear'
- 'log'
min_value:
name: minimum value
description: The minimum value allowed to be set
example: 0
selector:
text:
max_value:
name: Maximum value
description: The maximum value allowed to be set
example: 500
selector:
text:
min_position:
name: Minimum position
description: The minimum position the dial hand can rotate to generally.
selector:
number:
min: 0
max: 360
max_position:
name: Maximum position
description: The maximum position the dial hand can rotate to generally.
selector:
number:
min: 0
max: 360
set_nimbus_dial_state:
name: Set nimbus dial state
description: Set the value and labels of an individual nimbus dial
target:
entity:
integration: wink
fields:
value:
name: Value
description: The value that should be set (Should be between min_value and max_value)
required: true
example: 250
selector:
text:
labels:
name: Labels
description: >-
The values shown on the dial labels ["Dial 1", "test"] the first value
is what is shown by default the second value is shown when the nimbus is
pressed.
example: ["example", "test"]
selector:
object:
set_lock_vacation_mode:
name: Set lock vacation mode
description: Set vacation mode for all or specified locks. Disables all user codes.
fields:
entity_id:
name: Entity
description: Name of lock to unlock.
selector:
entity:
integration: wink
domain: lock
enabled:
name: Enabled
description: enable or disable. true or false.
required: true
selector:
boolean:
set_lock_alarm_mode:
name: Set lock alarm mode
description: Set alarm mode for all or specified locks.
fields:
entity_id:
name: Entity
description: Name of lock to unlock.
selector:
entity:
integration: wink
domain: lock
mode:
name: Mode
description: Select mode.
required: true
selector:
select:
options:
- 'activity'
- 'forced_entry'
- 'tamper'
set_lock_alarm_sensitivity:
name: Set lock alarm sensitivity
description: Set alarm sensitivity for all or specified locks.
fields:
entity_id:
name: Entity
description: Name of lock to unlock.
selector:
entity:
integration: wink
domain: lock
sensitivity:
name: Sensitivity
description: Choose the sensitivity.
required: true
selector:
select:
options:
- 'low'
- 'medium_low'
- 'medium'
- 'medium_high'
- 'high'
set_lock_alarm_state:
name: Set lok alarm state
description: Set alarm state.
fields:
entity_id:
name: Entity
description: Name of lock to unlock.
selector:
entity:
integration: wink
domain: lock
enabled:
name: Enabled
description: enable or disable.
required: true
selector:
boolean:
set_lock_beeper_state:
name: Set lock beeper state
description: Set beeper state.
fields:
entity_id:
name: Entity
description: Name of lock to unlock.
selector:
entity:
integration: wink
domain: lock
enabled:
name: Enabled
description: enable or disable.
required: true
selector:
boolean:
add_new_lock_key_code:
name: Add new lock key code
description: Add a new user key code.
fields:
entity_id:
name: Entity
description: Name of lock to unlock.
selector:
entity:
integration: wink
domain: lock
name:
name: Name
description: name of the new key code.
required: true
example: Bob
selector:
text:
code:
name: Code
description: new key code, length must match length of other codes. Default length is 4.
required: true
example: 1234
selector:
text:

View File

@ -1,60 +0,0 @@
"""Support for Wink switches."""
import pywink
from homeassistant.helpers.entity import ToggleEntity
from . import DOMAIN, WinkDevice
def setup_platform(hass, config, add_entities, discovery_info=None):
"""Set up the Wink platform."""
for switch in pywink.get_switches():
_id = switch.object_id() + switch.name()
if _id not in hass.data[DOMAIN]["unique_ids"]:
add_entities([WinkToggleDevice(switch, hass)])
for switch in pywink.get_powerstrips():
_id = switch.object_id() + switch.name()
if _id not in hass.data[DOMAIN]["unique_ids"]:
add_entities([WinkToggleDevice(switch, hass)])
for sprinkler in pywink.get_sprinklers():
_id = sprinkler.object_id() + sprinkler.name()
if _id not in hass.data[DOMAIN]["unique_ids"]:
add_entities([WinkToggleDevice(sprinkler, hass)])
for switch in pywink.get_binary_switch_groups():
_id = switch.object_id() + switch.name()
if _id not in hass.data[DOMAIN]["unique_ids"]:
add_entities([WinkToggleDevice(switch, hass)])
class WinkToggleDevice(WinkDevice, ToggleEntity):
"""Representation of a Wink toggle device."""
async def async_added_to_hass(self):
"""Call when entity is added to hass."""
self.hass.data[DOMAIN]["entities"]["switch"].append(self)
@property
def is_on(self):
"""Return true if device is on."""
return self.wink.state()
def turn_on(self, **kwargs):
"""Turn the device on."""
self.wink.set_state(True)
def turn_off(self, **kwargs):
"""Turn the device off."""
self.wink.set_state(False)
@property
def extra_state_attributes(self):
"""Return the state attributes."""
attributes = super().extra_state_attributes
try:
event = self.wink.last_event()
if event is not None:
attributes["last_event"] = event
except AttributeError:
pass
return attributes

View File

@ -1,143 +0,0 @@
"""Support for Wink water heaters."""
import logging
import pywink
from homeassistant.components.water_heater import (
ATTR_TEMPERATURE,
STATE_ECO,
STATE_ELECTRIC,
STATE_GAS,
STATE_HEAT_PUMP,
STATE_HIGH_DEMAND,
STATE_PERFORMANCE,
SUPPORT_AWAY_MODE,
SUPPORT_OPERATION_MODE,
SUPPORT_TARGET_TEMPERATURE,
WaterHeaterEntity,
)
from homeassistant.const import STATE_OFF, STATE_UNKNOWN, TEMP_CELSIUS
from . import DOMAIN, WinkDevice
_LOGGER = logging.getLogger(__name__)
SUPPORT_FLAGS_HEATER = (
SUPPORT_TARGET_TEMPERATURE | SUPPORT_OPERATION_MODE | SUPPORT_AWAY_MODE
)
ATTR_RHEEM_TYPE = "rheem_type"
ATTR_VACATION_MODE = "vacation_mode"
HA_STATE_TO_WINK = {
STATE_ECO: "eco",
STATE_ELECTRIC: "electric_only",
STATE_GAS: "gas",
STATE_HEAT_PUMP: "heat_pump",
STATE_HIGH_DEMAND: "high_demand",
STATE_OFF: "off",
STATE_PERFORMANCE: "performance",
}
WINK_STATE_TO_HA = {value: key for key, value in HA_STATE_TO_WINK.items()}
def setup_platform(hass, config, add_entities, discovery_info=None):
"""Set up the Wink water heater devices."""
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_entities([WinkWaterHeater(water_heater, hass)])
class WinkWaterHeater(WinkDevice, WaterHeaterEntity):
"""Representation of a Wink water heater."""
@property
def supported_features(self):
"""Return the list of supported features."""
return SUPPORT_FLAGS_HEATER
@property
def temperature_unit(self):
"""Return the unit of measurement."""
# The Wink API always returns temp in Celsius
return TEMP_CELSIUS
@property
def extra_state_attributes(self):
"""Return the optional device state attributes."""
data = {}
data[ATTR_VACATION_MODE] = self.wink.vacation_mode_enabled()
data[ATTR_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 = (
"Invalid operation mode mapping. "
f"{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

@ -1259,9 +1259,6 @@ proxmoxer==1.1.1
# homeassistant.components.systemmonitor
psutil==5.8.0
# homeassistant.components.wink
pubnubsub-handler==1.0.9
# homeassistant.components.pulseaudio_loopback
pulsectl==20.2.4
@ -1948,9 +1945,6 @@ python-vlc==1.1.2
# homeassistant.components.whois
python-whois==0.7.3
# homeassistant.components.wink
python-wink==1.10.5
# homeassistant.components.awair
python_awair==0.2.1

View File

@ -62,7 +62,6 @@ ALLOWED_IGNORE_VIOLATIONS = {
("velux", "scene.py"),
("wemo", "config_flow.py"),
("wiffi", "config_flow.py"),
("wink", "scene.py"),
}