mirror of
https://github.com/home-assistant/core.git
synced 2025-07-22 20:57:21 +00:00
Add config flow and 2FA support for Blink (#35396)
This commit is contained in:
parent
0a94d9b284
commit
85726b67b7
@ -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
|
||||
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,
|
||||
)
|
||||
|
||||
_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)]
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
SENSOR_SCHEMA = vol.Schema(
|
||||
{
|
||||
vol.Optional(CONF_MONITORED_CONDITIONS, default=list(SENSORS)): vol.All(
|
||||
cv.ensure_list, [vol.In(SENSORS)]
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
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 @@ CONFIG_SCHEMA = vol.Schema(
|
||||
{
|
||||
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 @@ CONFIG_SCHEMA = vol.Schema(
|
||||
)
|
||||
|
||||
|
||||
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,
|
||||
)
|
||||
hass.data[BLINK_DATA].refresh_rate = scan_interval.total_seconds()
|
||||
hass.data[BLINK_DATA].start()
|
||||
blink.refresh_rate = entry.data[CONF_SCAN_INTERVAL]
|
||||
|
||||
platforms = [
|
||||
("alarm_control_panel", {}),
|
||||
("binary_sensor", conf[CONF_BINARY_SENSORS]),
|
||||
("camera", {}),
|
||||
("sensor", conf[CONF_SENSORS]),
|
||||
]
|
||||
try:
|
||||
blink.login_response = entry.data["login_response"]
|
||||
blink.setup_params(entry.data["login_response"])
|
||||
except KeyError:
|
||||
blink.get_auth_token()
|
||||
|
||||
for component, schema in platforms:
|
||||
discovery.load_platform(hass, component, DOMAIN, schema, config)
|
||||
blink.setup_params(entry.data["login_response"])
|
||||
blink.setup_post_verify()
|
||||
return blink
|
||||
|
||||
def trigger_camera(call):
|
||||
"""Trigger a camera."""
|
||||
cameras = hass.data[BLINK_DATA].cameras
|
||||
name = call.data[CONF_NAME]
|
||||
if name in cameras:
|
||||
cameras[name].snap_picture()
|
||||
hass.data[BLINK_DATA].refresh(force_cache=True)
|
||||
|
||||
def blink_refresh(event_time):
|
||||
"""Call blink to refresh info."""
|
||||
hass.data[BLINK_DATA].refresh(force_cache=True)
|
||||
async def async_setup(hass, config):
|
||||
"""Set up a config entry."""
|
||||
hass.data[DOMAIN] = {}
|
||||
if DOMAIN not in config:
|
||||
return True
|
||||
|
||||
async def async_save_video(call):
|
||||
"""Call save video service handler."""
|
||||
await async_handle_save_video_service(hass, call)
|
||||
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
|
||||
)
|
||||
)
|
||||
|
||||
hass.services.register(DOMAIN, SERVICE_REFRESH, blink_refresh)
|
||||
hass.services.register(
|
||||
DOMAIN, SERVICE_TRIGGER, trigger_camera, schema=SERVICE_TRIGGER_SCHEMA
|
||||
)
|
||||
hass.services.register(
|
||||
DOMAIN, SERVICE_SAVE_VIDEO, async_save_video, schema=SERVICE_SAVE_VIDEO_SCHEMA
|
||||
)
|
||||
return True
|
||||
|
||||
|
||||
async def async_handle_save_video_service(hass, call):
|
||||
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
|
||||
)
|
||||
|
||||
if not hass.data[DOMAIN][entry.entry_id].available:
|
||||
_LOGGER.error("Blink unavailable for setup")
|
||||
return False
|
||||
|
||||
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[DOMAIN][entry.entry_id].cameras
|
||||
name = call.data[CONF_NAME]
|
||||
if name in cameras:
|
||||
cameras[name].snap_picture()
|
||||
blink_refresh()
|
||||
|
||||
def blink_refresh(event_time=None):
|
||||
"""Call blink to refresh info."""
|
||||
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, entry, call)
|
||||
|
||||
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.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, 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)
|
||||
|
||||
|
@ -9,23 +9,21 @@ from homeassistant.const import (
|
||||
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 @@ class BlinkSyncModule(AlarmControlPanelEntity):
|
||||
@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):
|
||||
|
@ -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 @@ class BlinkBinarySensor(BinarySensorEntity):
|
||||
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
|
||||
|
@ -3,7 +3,7 @@ import logging
|
||||
|
||||
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_VIDEO_CLIP = "video"
|
||||
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 @@ class BlinkCamera(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
|
||||
|
115
homeassistant/components/blink/config_flow.py
Normal file
115
homeassistant/components/blink/config_flow.py
Normal file
@ -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."""
|
25
homeassistant/components/blink/const.py
Normal file
25
homeassistant/components/blink/const.py
Normal file
@ -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")
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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"
|
||||
|
25
homeassistant/components/blink/strings.json
Normal file
25
homeassistant/components/blink/strings.json
Normal file
@ -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%]"
|
||||
}
|
||||
}
|
||||
}
|
28
homeassistant/components/blink/translations/en.json
Normal file
28
homeassistant/components/blink/translations/en.json
Normal file
@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -18,6 +18,7 @@ FLOWS = [
|
||||
"august",
|
||||
"axis",
|
||||
"blebox",
|
||||
"blink",
|
||||
"braviatv",
|
||||
"brother",
|
||||
"bsblan",
|
||||
|
@ -345,7 +345,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
|
||||
|
Loading…
x
Reference in New Issue
Block a user