diff --git a/homeassistant/components/blink/__init__.py b/homeassistant/components/blink/__init__.py index e233a8b21d8196..3576574c357f58 100644 --- a/homeassistant/components/blink/__init__.py +++ b/homeassistant/components/blink/__init__.py @@ -1,81 +1,40 @@ """Support for Blink Home Camera System.""" -from datetime import timedelta +import asyncio import logging -from blinkpy import blinkpy +from blinkpy.blinkpy import Blink import voluptuous as vol +from homeassistant.config_entries import SOURCE_IMPORT from homeassistant.const import ( - CONF_BINARY_SENSORS, CONF_FILENAME, - CONF_MODE, - CONF_MONITORED_CONDITIONS, CONF_NAME, - CONF_OFFSET, CONF_PASSWORD, + CONF_PIN, CONF_SCAN_INTERVAL, - CONF_SENSORS, CONF_USERNAME, - TEMP_FAHRENHEIT, ) -from homeassistant.helpers import config_validation as cv, discovery - -_LOGGER = logging.getLogger(__name__) - -DOMAIN = "blink" -BLINK_DATA = "blink" - -CONF_CAMERA = "camera" -CONF_ALARM_CONTROL_PANEL = "alarm_control_panel" - -DEFAULT_BRAND = "Blink" -DEFAULT_ATTRIBUTION = "Data provided by immedia-semi.com" -SIGNAL_UPDATE_BLINK = "blink_update" - -DEFAULT_SCAN_INTERVAL = timedelta(seconds=300) - -TYPE_CAMERA_ARMED = "motion_enabled" -TYPE_MOTION_DETECTED = "motion_detected" -TYPE_TEMPERATURE = "temperature" -TYPE_BATTERY = "battery" -TYPE_WIFI_STRENGTH = "wifi_strength" - -SERVICE_REFRESH = "blink_update" -SERVICE_TRIGGER = "trigger_camera" -SERVICE_SAVE_VIDEO = "save_video" - -BINARY_SENSORS = { - TYPE_CAMERA_ARMED: ["Camera Armed", "mdi:verified"], - TYPE_MOTION_DETECTED: ["Motion Detected", "mdi:run-fast"], -} - -SENSORS = { - TYPE_TEMPERATURE: ["Temperature", TEMP_FAHRENHEIT, "mdi:thermometer"], - TYPE_BATTERY: ["Battery", "", "mdi:battery-80"], - TYPE_WIFI_STRENGTH: ["Wifi Signal", "dBm", "mdi:wifi-strength-2"], -} - -BINARY_SENSOR_SCHEMA = vol.Schema( - { - vol.Optional(CONF_MONITORED_CONDITIONS, default=list(BINARY_SENSORS)): vol.All( - cv.ensure_list, [vol.In(BINARY_SENSORS)] - ) - } +from homeassistant.helpers import config_validation as cv + +from .const import ( + DEFAULT_OFFSET, + DEFAULT_SCAN_INTERVAL, + DEVICE_ID, + DOMAIN, + PLATFORMS, + SERVICE_REFRESH, + SERVICE_SAVE_VIDEO, + SERVICE_SEND_PIN, + SERVICE_TRIGGER, ) -SENSOR_SCHEMA = vol.Schema( - { - vol.Optional(CONF_MONITORED_CONDITIONS, default=list(SENSORS)): vol.All( - cv.ensure_list, [vol.In(SENSORS)] - ) - } -) +_LOGGER = logging.getLogger(__name__) SERVICE_TRIGGER_SCHEMA = vol.Schema({vol.Required(CONF_NAME): cv.string}) - SERVICE_SAVE_VIDEO_SCHEMA = vol.Schema( {vol.Required(CONF_NAME): cv.string, vol.Required(CONF_FILENAME): cv.string} ) +SERVICE_SEND_PIN_SCHEMA = vol.Schema({vol.Optional(CONF_PIN): cv.string}) CONFIG_SCHEMA = vol.Schema( { @@ -83,13 +42,7 @@ { vol.Required(CONF_USERNAME): cv.string, vol.Required(CONF_PASSWORD): cv.string, - vol.Optional( - CONF_SCAN_INTERVAL, default=DEFAULT_SCAN_INTERVAL - ): cv.time_period, - vol.Optional(CONF_BINARY_SENSORS, default={}): BINARY_SENSOR_SCHEMA, - vol.Optional(CONF_SENSORS, default={}): SENSOR_SCHEMA, - vol.Optional(CONF_OFFSET, default=1): int, - vol.Optional(CONF_MODE, default=""): cv.string, + vol.Optional(CONF_SCAN_INTERVAL, default=DEFAULT_SCAN_INTERVAL): int, } ) }, @@ -97,61 +50,127 @@ ) -def setup(hass, config): - """Set up Blink System.""" - - conf = config[BLINK_DATA] - username = conf[CONF_USERNAME] - password = conf[CONF_PASSWORD] - scan_interval = conf[CONF_SCAN_INTERVAL] - is_legacy = bool(conf[CONF_MODE] == "legacy") - motion_interval = conf[CONF_OFFSET] - hass.data[BLINK_DATA] = blinkpy.Blink( - username=username, - password=password, - motion_interval=motion_interval, - legacy_subdomain=is_legacy, +def _blink_startup_wrapper(entry): + """Startup wrapper for blink.""" + blink = Blink( + username=entry.data[CONF_USERNAME], + password=entry.data[CONF_PASSWORD], + motion_interval=DEFAULT_OFFSET, + legacy_subdomain=False, + no_prompt=True, + device_id=DEVICE_ID, + ) + blink.refresh_rate = entry.data[CONF_SCAN_INTERVAL] + + try: + blink.login_response = entry.data["login_response"] + blink.setup_params(entry.data["login_response"]) + except KeyError: + blink.get_auth_token() + + blink.setup_params(entry.data["login_response"]) + blink.setup_post_verify() + return blink + + +async def async_setup(hass, config): + """Set up a config entry.""" + hass.data[DOMAIN] = {} + if DOMAIN not in config: + return True + + conf = config.get(DOMAIN, {}) + + if not hass.config_entries.async_entries(DOMAIN): + hass.async_create_task( + hass.config_entries.flow.async_init( + DOMAIN, context={"source": SOURCE_IMPORT}, data=conf + ) + ) + + return True + + +async def async_setup_entry(hass, entry): + """Set up Blink via config entry.""" + hass.data[DOMAIN][entry.entry_id] = await hass.async_add_executor_job( + _blink_startup_wrapper, entry ) - hass.data[BLINK_DATA].refresh_rate = scan_interval.total_seconds() - hass.data[BLINK_DATA].start() - platforms = [ - ("alarm_control_panel", {}), - ("binary_sensor", conf[CONF_BINARY_SENSORS]), - ("camera", {}), - ("sensor", conf[CONF_SENSORS]), - ] + if not hass.data[DOMAIN][entry.entry_id].available: + _LOGGER.error("Blink unavailable for setup") + return False - for component, schema in platforms: - discovery.load_platform(hass, component, DOMAIN, schema, config) + for component in PLATFORMS: + hass.async_create_task( + hass.config_entries.async_forward_entry_setup(entry, component) + ) def trigger_camera(call): """Trigger a camera.""" - cameras = hass.data[BLINK_DATA].cameras + cameras = hass.data[DOMAIN][entry.entry_id].cameras name = call.data[CONF_NAME] if name in cameras: cameras[name].snap_picture() - hass.data[BLINK_DATA].refresh(force_cache=True) + blink_refresh() - def blink_refresh(event_time): + def blink_refresh(event_time=None): """Call blink to refresh info.""" - hass.data[BLINK_DATA].refresh(force_cache=True) + hass.data[DOMAIN][entry.entry_id].refresh(force_cache=True) async def async_save_video(call): """Call save video service handler.""" - await async_handle_save_video_service(hass, call) + await async_handle_save_video_service(hass, entry, call) - hass.services.register(DOMAIN, SERVICE_REFRESH, blink_refresh) - hass.services.register( + def send_pin(call): + """Call blink to send new pin.""" + pin = call.data[CONF_PIN] + hass.data[DOMAIN][entry.entry_id].login_handler.send_auth_key( + hass.data[DOMAIN][entry.entry_id], pin, + ) + + hass.services.async_register(DOMAIN, SERVICE_REFRESH, blink_refresh) + hass.services.async_register( DOMAIN, SERVICE_TRIGGER, trigger_camera, schema=SERVICE_TRIGGER_SCHEMA ) - hass.services.register( + hass.services.async_register( DOMAIN, SERVICE_SAVE_VIDEO, async_save_video, schema=SERVICE_SAVE_VIDEO_SCHEMA ) + hass.services.async_register( + DOMAIN, SERVICE_SEND_PIN, send_pin, schema=SERVICE_SEND_PIN_SCHEMA + ) + + return True + + +async def async_unload_entry(hass, entry): + """Unload Blink entry.""" + unload_ok = all( + await asyncio.gather( + *[ + hass.config_entries.async_forward_entry_unload(entry, component) + for component in PLATFORMS + ] + ) + ) + + if not unload_ok: + return False + + hass.data[DOMAIN].pop(entry.entry_id) + + if len(hass.data[DOMAIN]) != 0: + return True + + hass.services.async_remove(DOMAIN, SERVICE_REFRESH) + hass.services.async_remove(DOMAIN, SERVICE_TRIGGER) + hass.services.async_remove(DOMAIN, SERVICE_SAVE_VIDEO_SCHEMA) + hass.services.async_remove(DOMAIN, SERVICE_SEND_PIN) + return True -async def async_handle_save_video_service(hass, call): +async def async_handle_save_video_service(hass, entry, call): """Handle save video service calls.""" camera_name = call.data[CONF_NAME] video_path = call.data[CONF_FILENAME] @@ -161,7 +180,7 @@ async def async_handle_save_video_service(hass, call): def _write_video(camera_name, video_path): """Call video write.""" - all_cameras = hass.data[BLINK_DATA].cameras + all_cameras = hass.data[DOMAIN][entry.entry_id].cameras if camera_name in all_cameras: all_cameras[camera_name].video_to_file(video_path) diff --git a/homeassistant/components/blink/alarm_control_panel.py b/homeassistant/components/blink/alarm_control_panel.py index e6af3780aafa89..1ca4c4beac9c42 100644 --- a/homeassistant/components/blink/alarm_control_panel.py +++ b/homeassistant/components/blink/alarm_control_panel.py @@ -9,23 +9,21 @@ STATE_ALARM_DISARMED, ) -from . import BLINK_DATA, DEFAULT_ATTRIBUTION +from .const import DEFAULT_ATTRIBUTION, DOMAIN _LOGGER = logging.getLogger(__name__) ICON = "mdi:security" -def setup_platform(hass, config, add_entities, discovery_info=None): - """Set up the Arlo Alarm Control Panels.""" - if discovery_info is None: - return - data = hass.data[BLINK_DATA] +async def async_setup_entry(hass, config, async_add_entities): + """Set up the Blink Alarm Control Panels.""" + data = hass.data[DOMAIN][config.entry_id] sync_modules = [] for sync_name, sync_module in data.sync.items(): sync_modules.append(BlinkSyncModule(data, sync_name, sync_module)) - add_entities(sync_modules, True) + async_add_entities(sync_modules) class BlinkSyncModule(AlarmControlPanelEntity): @@ -61,7 +59,7 @@ def supported_features(self) -> int: @property def name(self): """Return the name of the panel.""" - return f"{BLINK_DATA} {self._name}" + return f"{DOMAIN} {self._name}" @property def device_state_attributes(self): diff --git a/homeassistant/components/blink/binary_sensor.py b/homeassistant/components/blink/binary_sensor.py index 219b9fb8cd36de..8c86622b74e81c 100644 --- a/homeassistant/components/blink/binary_sensor.py +++ b/homeassistant/components/blink/binary_sensor.py @@ -1,21 +1,23 @@ """Support for Blink system camera control.""" from homeassistant.components.binary_sensor import BinarySensorEntity -from homeassistant.const import CONF_MONITORED_CONDITIONS -from . import BINARY_SENSORS, BLINK_DATA +from .const import DOMAIN, TYPE_CAMERA_ARMED, TYPE_MOTION_DETECTED +BINARY_SENSORS = { + TYPE_CAMERA_ARMED: ["Camera Armed", "mdi:verified"], + TYPE_MOTION_DETECTED: ["Motion Detected", "mdi:run-fast"], +} -def setup_platform(hass, config, add_entities, discovery_info=None): + +async def async_setup_entry(hass, config, async_add_entities): """Set up the blink binary sensors.""" - if discovery_info is None: - return - data = hass.data[BLINK_DATA] + data = hass.data[DOMAIN][config.entry_id] - devs = [] + entities = [] for camera in data.cameras: - for sensor_type in discovery_info[CONF_MONITORED_CONDITIONS]: - devs.append(BlinkBinarySensor(data, camera, sensor_type)) - add_entities(devs, True) + for sensor_type in BINARY_SENSORS: + entities.append(BlinkBinarySensor(data, camera, sensor_type)) + async_add_entities(entities) class BlinkBinarySensor(BinarySensorEntity): @@ -26,7 +28,7 @@ def __init__(self, data, camera, sensor_type): self.data = data self._type = sensor_type name, icon = BINARY_SENSORS[sensor_type] - self._name = f"{BLINK_DATA} {camera} {name}" + self._name = f"{DOMAIN} {camera} {name}" self._icon = icon self._camera = data.cameras[camera] self._state = None diff --git a/homeassistant/components/blink/camera.py b/homeassistant/components/blink/camera.py index 52043324a40f99..c675d4dda56f83 100644 --- a/homeassistant/components/blink/camera.py +++ b/homeassistant/components/blink/camera.py @@ -3,7 +3,7 @@ from homeassistant.components.camera import Camera -from . import BLINK_DATA, DEFAULT_BRAND +from .const import DEFAULT_BRAND, DOMAIN _LOGGER = logging.getLogger(__name__) @@ -11,16 +11,14 @@ ATTR_IMAGE = "image" -def setup_platform(hass, config, add_entities, discovery_info=None): +async def async_setup_entry(hass, config, async_add_entities): """Set up a Blink Camera.""" - if discovery_info is None: - return - data = hass.data[BLINK_DATA] - devs = [] + data = hass.data[DOMAIN][config.entry_id] + entities = [] for name, camera in data.cameras.items(): - devs.append(BlinkCamera(data, name, camera)) + entities.append(BlinkCamera(data, name, camera)) - add_entities(devs) + async_add_entities(entities) class BlinkCamera(Camera): @@ -30,7 +28,7 @@ def __init__(self, data, name, camera): """Initialize a camera.""" super().__init__() self.data = data - self._name = f"{BLINK_DATA} {name}" + self._name = f"{DOMAIN} {name}" self._camera = camera self._unique_id = f"{camera.serial}-camera" self.response = None diff --git a/homeassistant/components/blink/config_flow.py b/homeassistant/components/blink/config_flow.py new file mode 100644 index 00000000000000..281dee17cb17d5 --- /dev/null +++ b/homeassistant/components/blink/config_flow.py @@ -0,0 +1,115 @@ +"""Config flow to configure Blink.""" +import logging + +from blinkpy.blinkpy import Blink +import voluptuous as vol + +from homeassistant import config_entries, core, exceptions +from homeassistant.const import ( + CONF_PASSWORD, + CONF_PIN, + CONF_SCAN_INTERVAL, + CONF_USERNAME, +) + +from .const import DEFAULT_OFFSET, DEFAULT_SCAN_INTERVAL, DEVICE_ID, DOMAIN + +_LOGGER = logging.getLogger(__name__) + + +async def validate_input(hass: core.HomeAssistant, blink): + """Validate the user input allows us to connect.""" + response = await hass.async_add_executor_job(blink.get_auth_token) + if not response: + raise InvalidAuth + if blink.key_required: + raise Require2FA + + return blink.login_response + + +class BlinkConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): + """Handle a Blink config flow.""" + + VERSION = 1 + CONNECTION_CLASS = config_entries.CONN_CLASS_CLOUD_POLL + + def __init__(self): + """Initialize the blink flow.""" + self.blink = None + self.data = { + CONF_USERNAME: "", + CONF_PASSWORD: "", + CONF_SCAN_INTERVAL: DEFAULT_SCAN_INTERVAL, + "login_response": None, + } + + async def async_step_user(self, user_input=None): + """Handle a flow initiated by the user.""" + errors = {} + if user_input is not None: + self.data[CONF_USERNAME] = user_input["username"] + self.data[CONF_PASSWORD] = user_input["password"] + + await self.async_set_unique_id(self.data[CONF_USERNAME]) + + if CONF_SCAN_INTERVAL in user_input: + self.data[CONF_SCAN_INTERVAL] = user_input["scan_interval"] + + self.blink = Blink( + username=self.data[CONF_USERNAME], + password=self.data[CONF_PASSWORD], + motion_interval=DEFAULT_OFFSET, + legacy_subdomain=False, + no_prompt=True, + device_id=DEVICE_ID, + ) + + try: + response = await validate_input(self.hass, self.blink) + self.data["login_response"] = response + return self.async_create_entry(title=DOMAIN, data=self.data,) + except Require2FA: + return await self.async_step_2fa() + except InvalidAuth: + errors["base"] = "invalid_auth" + except Exception: # pylint: disable=broad-except + _LOGGER.exception("Unexpected exception") + errors["base"] = "unknown" + + data_schema = { + vol.Required("username"): str, + vol.Required("password"): str, + } + + return self.async_show_form( + step_id="user", data_schema=vol.Schema(data_schema), errors=errors, + ) + + async def async_step_2fa(self, user_input=None): + """Handle 2FA step.""" + if user_input is not None: + pin = user_input.get(CONF_PIN) + if await self.hass.async_add_executor_job( + self.blink.login_handler.send_auth_key, self.blink, pin + ): + return await self.async_step_user(user_input=self.data) + + return self.async_show_form( + step_id="2fa", + data_schema=vol.Schema( + {vol.Optional("pin"): vol.All(str, vol.Length(min=1))} + ), + ) + + async def async_step_import(self, import_data): + """Import blink config from configuration.yaml.""" + return await self.async_step_user(import_data) + + +class Require2FA(exceptions.HomeAssistantError): + """Error to indicate we require 2FA.""" + + +class InvalidAuth(exceptions.HomeAssistantError): + """Error to indicate there is invalid auth.""" diff --git a/homeassistant/components/blink/const.py b/homeassistant/components/blink/const.py new file mode 100644 index 00000000000000..5ce22d1091467e --- /dev/null +++ b/homeassistant/components/blink/const.py @@ -0,0 +1,25 @@ +"""Constants for Blink.""" +DOMAIN = "blink" +DEVICE_ID = "Home Assistant" + +CONF_CAMERA = "camera" +CONF_ALARM_CONTROL_PANEL = "alarm_control_panel" + +DEFAULT_BRAND = "Blink" +DEFAULT_ATTRIBUTION = "Data provided by immedia-semi.com" +DEFAULT_SCAN_INTERVAL = 300 +DEFAULT_OFFSET = 1 +SIGNAL_UPDATE_BLINK = "blink_update" + +TYPE_CAMERA_ARMED = "motion_enabled" +TYPE_MOTION_DETECTED = "motion_detected" +TYPE_TEMPERATURE = "temperature" +TYPE_BATTERY = "battery" +TYPE_WIFI_STRENGTH = "wifi_strength" + +SERVICE_REFRESH = "blink_update" +SERVICE_TRIGGER = "trigger_camera" +SERVICE_SAVE_VIDEO = "save_video" +SERVICE_SEND_PIN = "send_pin" + +PLATFORMS = ("alarm_control_panel", "binary_sensor", "camera", "sensor") diff --git a/homeassistant/components/blink/manifest.json b/homeassistant/components/blink/manifest.json index d55510c44ad722..ac42870fdb79e7 100644 --- a/homeassistant/components/blink/manifest.json +++ b/homeassistant/components/blink/manifest.json @@ -2,6 +2,7 @@ "domain": "blink", "name": "Blink", "documentation": "https://www.home-assistant.io/integrations/blink", - "requirements": ["blinkpy==0.14.3"], - "codeowners": ["@fronzbot"] + "requirements": ["blinkpy==0.15.0"], + "codeowners": ["@fronzbot"], + "config_flow": true } diff --git a/homeassistant/components/blink/sensor.py b/homeassistant/components/blink/sensor.py index 81616b463ecfbe..eb9e309fc65b61 100644 --- a/homeassistant/components/blink/sensor.py +++ b/homeassistant/components/blink/sensor.py @@ -1,25 +1,29 @@ """Support for Blink system camera sensors.""" import logging -from homeassistant.const import CONF_MONITORED_CONDITIONS +from homeassistant.const import TEMP_FAHRENHEIT from homeassistant.helpers.entity import Entity -from . import BLINK_DATA, SENSORS +from .const import DOMAIN, TYPE_BATTERY, TYPE_TEMPERATURE, TYPE_WIFI_STRENGTH _LOGGER = logging.getLogger(__name__) +SENSORS = { + TYPE_TEMPERATURE: ["Temperature", TEMP_FAHRENHEIT, "mdi:thermometer"], + TYPE_BATTERY: ["Battery", "", "mdi:battery-80"], + TYPE_WIFI_STRENGTH: ["Wifi Signal", "dBm", "mdi:wifi-strength-2"], +} -def setup_platform(hass, config, add_entities, discovery_info=None): - """Set up a Blink sensor.""" - if discovery_info is None: - return - data = hass.data[BLINK_DATA] - devs = [] + +async def async_setup_entry(hass, config, async_add_entities): + """Initialize a Blink sensor.""" + data = hass.data[DOMAIN][config.entry_id] + entities = [] for camera in data.cameras: - for sensor_type in discovery_info[CONF_MONITORED_CONDITIONS]: - devs.append(BlinkSensor(data, camera, sensor_type)) + for sensor_type in SENSORS: + entities.append(BlinkSensor(data, camera, sensor_type)) - add_entities(devs, True) + async_add_entities(entities) class BlinkSensor(Entity): @@ -28,7 +32,7 @@ class BlinkSensor(Entity): def __init__(self, data, camera, sensor_type): """Initialize sensors from Blink camera.""" name, units, icon = SENSORS[sensor_type] - self._name = f"{BLINK_DATA} {camera} {name}" + self._name = f"{DOMAIN} {camera} {name}" self._camera_name = name self._type = sensor_type self.data = data diff --git a/homeassistant/components/blink/services.yaml b/homeassistant/components/blink/services.yaml index 37595837c11877..9a4d00ee0b8176 100644 --- a/homeassistant/components/blink/services.yaml +++ b/homeassistant/components/blink/services.yaml @@ -19,3 +19,10 @@ save_video: filename: description: Filename to writable path (directory may need to be included in whitelist_dirs in config) example: "/tmp/video.mp4" + +send_pin: + description: Send a new pin to blink for 2FA. + fields: + pin: + description: Pin received from blink. Leave empty if you only received a verification email. + example: "abc123" diff --git a/homeassistant/components/blink/strings.json b/homeassistant/components/blink/strings.json new file mode 100644 index 00000000000000..dcd4a488c5c98a --- /dev/null +++ b/homeassistant/components/blink/strings.json @@ -0,0 +1,25 @@ +{ + "config": { + "step": { + "user": { + "title": "Sign-in with Blink account", + "data": { + "username": "[%key:common::config_flow::data::username%]", + "password": "[%key:common::config_flow::data::password%]" + } + }, + "2fa": { + "title": "Two-factor authentication", + "data": { "2fa": "Two-factor code" }, + "description": "Enter the pin sent to your email. If the email does not contain a pin, leave blank" + } + }, + "error": { + "invalid_auth": "[%key:common::config_flow::error::invalid_auth%]", + "unknown": "[%key:common::config_flow::error::unknown%]" + }, + "abort": { + "already_configured": "[%key:common::config_flow::abort::already_configured_device%]" + } + } +} diff --git a/homeassistant/components/blink/translations/en.json b/homeassistant/components/blink/translations/en.json new file mode 100644 index 00000000000000..dd3ed091b2ed0a --- /dev/null +++ b/homeassistant/components/blink/translations/en.json @@ -0,0 +1,28 @@ +{ + "config": { + "abort": { + "already_configured": "Device is already configured" + }, + "error": { + "invalid_auth": "Invalid authentication", + "unknown": "Unexpected error" + }, + "step": { + "2fa": { + "data": { + "2fa": "Two-factor code" + }, + "title": "Two-factor authentication", + "description": "Enter the pin sent to your email. If the email does not contain a pin, leave blank" + }, + "user": { + "data": { + "password": "Password", + "username": "Username", + "scan_interval": "Scan Interval" + }, + "title": "Sign-in with Blink account" + } + } + } +} diff --git a/homeassistant/generated/config_flows.py b/homeassistant/generated/config_flows.py index 817109dfe95595..0c6eff4c7e6946 100644 --- a/homeassistant/generated/config_flows.py +++ b/homeassistant/generated/config_flows.py @@ -17,6 +17,7 @@ "august", "axis", "blebox", + "blink", "braviatv", "brother", "cast", diff --git a/requirements_all.txt b/requirements_all.txt index 9e6910e54784f1..227ceb2de4aa8c 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -336,7 +336,7 @@ bizkaibus==0.1.1 blebox_uniapi==1.3.2 # homeassistant.components.blink -blinkpy==0.14.3 +blinkpy==0.15.0 # homeassistant.components.blinksticklight blinkstick==1.1.8