Bump Blinkpy to 0.22.2 in Blink (#98571)

This commit is contained in:
mkmer 2023-10-16 07:41:45 -04:00 committed by GitHub
parent 6b05f51413
commit eaf6197d43
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 144 additions and 107 deletions

View File

@ -1,7 +1,9 @@
"""Support for Blink Home Camera System.""" """Support for Blink Home Camera System."""
import asyncio
from copy import deepcopy from copy import deepcopy
import logging import logging
from aiohttp import ClientError
from blinkpy.auth import Auth from blinkpy.auth import Auth
from blinkpy.blinkpy import Blink from blinkpy.blinkpy import Blink
import voluptuous as vol import voluptuous as vol
@ -16,8 +18,9 @@ from homeassistant.const import (
CONF_SCAN_INTERVAL, CONF_SCAN_INTERVAL,
) )
from homeassistant.core import HomeAssistant, callback from homeassistant.core import HomeAssistant, callback
from homeassistant.exceptions import ConfigEntryNotReady from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady
from homeassistant.helpers import config_validation as cv from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from .const import ( from .const import (
DEFAULT_SCAN_INTERVAL, DEFAULT_SCAN_INTERVAL,
@ -40,23 +43,7 @@ SERVICE_SAVE_RECENT_CLIPS_SCHEMA = vol.Schema(
) )
def _blink_startup_wrapper(hass: HomeAssistant, entry: ConfigEntry) -> Blink: async def _reauth_flow_wrapper(hass, data):
"""Startup wrapper for blink."""
blink = Blink()
auth_data = deepcopy(dict(entry.data))
blink.auth = Auth(auth_data, no_prompt=True)
blink.refresh_rate = entry.options.get(CONF_SCAN_INTERVAL, DEFAULT_SCAN_INTERVAL)
if blink.start():
blink.setup_post_verify()
elif blink.auth.check_key_required():
_LOGGER.debug("Attempting a reauth flow")
_reauth_flow_wrapper(hass, auth_data)
return blink
def _reauth_flow_wrapper(hass, data):
"""Reauth flow wrapper.""" """Reauth flow wrapper."""
hass.add_job( hass.add_job(
hass.config_entries.flow.async_init( hass.config_entries.flow.async_init(
@ -79,10 +66,10 @@ async def async_migrate_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
data = {**entry.data} data = {**entry.data}
if entry.version == 1: if entry.version == 1:
data.pop("login_response", None) data.pop("login_response", None)
await hass.async_add_executor_job(_reauth_flow_wrapper, hass, data) await _reauth_flow_wrapper(hass, data)
return False return False
if entry.version == 2: if entry.version == 2:
await hass.async_add_executor_job(_reauth_flow_wrapper, hass, data) await _reauth_flow_wrapper(hass, data)
return False return False
return True return True
@ -92,19 +79,32 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
hass.data.setdefault(DOMAIN, {}) hass.data.setdefault(DOMAIN, {})
_async_import_options_from_data_if_missing(hass, entry) _async_import_options_from_data_if_missing(hass, entry)
hass.data[DOMAIN][entry.entry_id] = await hass.async_add_executor_job( session = async_get_clientsession(hass)
_blink_startup_wrapper, hass, entry blink = Blink(session=session)
) auth_data = deepcopy(dict(entry.data))
blink.auth = Auth(auth_data, no_prompt=True, session=session)
blink.refresh_rate = entry.options.get(CONF_SCAN_INTERVAL, DEFAULT_SCAN_INTERVAL)
if not hass.data[DOMAIN][entry.entry_id].available: try:
await blink.start()
if blink.auth.check_key_required():
_LOGGER.debug("Attempting a reauth flow")
raise ConfigEntryAuthFailed("Need 2FA for Blink")
except (ClientError, asyncio.TimeoutError) as ex:
raise ConfigEntryNotReady("Can not connect to host") from ex
hass.data[DOMAIN][entry.entry_id] = blink
if not blink.available:
raise ConfigEntryNotReady raise ConfigEntryNotReady
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS) await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
entry.async_on_unload(entry.add_update_listener(update_listener)) entry.async_on_unload(entry.add_update_listener(update_listener))
await blink.refresh(force=True)
def blink_refresh(event_time=None): async def blink_refresh(event_time=None):
"""Call blink to refresh info.""" """Call blink to refresh info."""
hass.data[DOMAIN][entry.entry_id].refresh(force_cache=True) await hass.data[DOMAIN][entry.entry_id].refresh(force_cache=True)
async def async_save_video(call): async def async_save_video(call):
"""Call save video service handler.""" """Call save video service handler."""
@ -114,10 +114,10 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Call save recent clips service handler.""" """Call save recent clips service handler."""
await async_handle_save_recent_clips_service(hass, entry, call) await async_handle_save_recent_clips_service(hass, entry, call)
def send_pin(call): async def send_pin(call):
"""Call blink to send new pin.""" """Call blink to send new pin."""
pin = call.data[CONF_PIN] pin = call.data[CONF_PIN]
hass.data[DOMAIN][entry.entry_id].auth.send_auth_key( await hass.data[DOMAIN][entry.entry_id].auth.send_auth_key(
hass.data[DOMAIN][entry.entry_id], hass.data[DOMAIN][entry.entry_id],
pin, pin,
) )
@ -176,27 +176,27 @@ async def update_listener(hass: HomeAssistant, entry: ConfigEntry) -> None:
blink.refresh_rate = entry.options[CONF_SCAN_INTERVAL] blink.refresh_rate = entry.options[CONF_SCAN_INTERVAL]
async def async_handle_save_video_service(hass, entry, call): async def async_handle_save_video_service(
hass: HomeAssistant, entry: ConfigEntry, call
) -> None:
"""Handle save video service calls.""" """Handle save video service calls."""
camera_name = call.data[CONF_NAME] camera_name = call.data[CONF_NAME]
video_path = call.data[CONF_FILENAME] video_path = call.data[CONF_FILENAME]
if not hass.config.is_allowed_path(video_path): if not hass.config.is_allowed_path(video_path):
_LOGGER.error("Can't write %s, no access to path!", video_path) _LOGGER.error("Can't write %s, no access to path!", video_path)
return return
def _write_video(name, file_path):
"""Call video write."""
all_cameras = hass.data[DOMAIN][entry.entry_id].cameras
if name in all_cameras:
all_cameras[name].video_to_file(file_path)
try: try:
await hass.async_add_executor_job(_write_video, camera_name, video_path) all_cameras = hass.data[DOMAIN][entry.entry_id].cameras
if camera_name in all_cameras:
await all_cameras[camera_name].video_to_file(video_path)
except OSError as err: except OSError as err:
_LOGGER.error("Can't write image to file: %s", err) _LOGGER.error("Can't write image to file: %s", err)
async def async_handle_save_recent_clips_service(hass, entry, call): async def async_handle_save_recent_clips_service(
hass: HomeAssistant, entry: ConfigEntry, call
) -> None:
"""Save multiple recent clips to output directory.""" """Save multiple recent clips to output directory."""
camera_name = call.data[CONF_NAME] camera_name = call.data[CONF_NAME]
clips_dir = call.data[CONF_FILE_PATH] clips_dir = call.data[CONF_FILE_PATH]
@ -204,13 +204,9 @@ async def async_handle_save_recent_clips_service(hass, entry, call):
_LOGGER.error("Can't write to directory %s, no access to path!", clips_dir) _LOGGER.error("Can't write to directory %s, no access to path!", clips_dir)
return return
def _save_recent_clips(name, output_dir):
"""Call save recent clips."""
all_cameras = hass.data[DOMAIN][entry.entry_id].cameras
if name in all_cameras:
all_cameras[name].save_recent_clips(output_dir=output_dir)
try: try:
await hass.async_add_executor_job(_save_recent_clips, camera_name, clips_dir) all_cameras = hass.data[DOMAIN][entry.entry_id].cameras
if camera_name in all_cameras:
await all_cameras[camera_name].save_recent_clips(output_dir=clips_dir)
except OSError as err: except OSError as err:
_LOGGER.error("Can't write recent clips to directory: %s", err) _LOGGER.error("Can't write recent clips to directory: %s", err)

View File

@ -1,8 +1,11 @@
"""Support for Blink Alarm Control Panel.""" """Support for Blink Alarm Control Panel."""
from __future__ import annotations from __future__ import annotations
import asyncio
import logging import logging
from blinkpy.blinkpy import Blink
from homeassistant.components.alarm_control_panel import ( from homeassistant.components.alarm_control_panel import (
AlarmControlPanelEntity, AlarmControlPanelEntity,
AlarmControlPanelEntityFeature, AlarmControlPanelEntityFeature,
@ -16,6 +19,7 @@ from homeassistant.const import (
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.helpers.device_registry import DeviceInfo from homeassistant.helpers.device_registry import DeviceInfo
from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.typing import StateType
from .const import DEFAULT_ATTRIBUTION, DEFAULT_BRAND, DOMAIN from .const import DEFAULT_ATTRIBUTION, DEFAULT_BRAND, DOMAIN
@ -32,11 +36,11 @@ async def async_setup_entry(
sync_modules = [] sync_modules = []
for sync_name, sync_module in data.sync.items(): for sync_name, sync_module in data.sync.items():
sync_modules.append(BlinkSyncModule(data, sync_name, sync_module)) sync_modules.append(BlinkSyncModuleHA(data, sync_name, sync_module))
async_add_entities(sync_modules) async_add_entities(sync_modules, update_before_add=True)
class BlinkSyncModule(AlarmControlPanelEntity): class BlinkSyncModuleHA(AlarmControlPanelEntity):
"""Representation of a Blink Alarm Control Panel.""" """Representation of a Blink Alarm Control Panel."""
_attr_icon = ICON _attr_icon = ICON
@ -44,19 +48,19 @@ class BlinkSyncModule(AlarmControlPanelEntity):
_attr_name = None _attr_name = None
_attr_has_entity_name = True _attr_has_entity_name = True
def __init__(self, data, name, sync): def __init__(self, data, name: str, sync) -> None:
"""Initialize the alarm control panel.""" """Initialize the alarm control panel."""
self.data = data self.data: Blink = data
self.sync = sync self.sync = sync
self._name = name self._name: str = name
self._attr_unique_id = sync.serial self._attr_unique_id: str = sync.serial
self._attr_device_info = DeviceInfo( self._attr_device_info = DeviceInfo(
identifiers={(DOMAIN, sync.serial)}, identifiers={(DOMAIN, sync.serial)},
name=f"{DOMAIN} {name}", name=f"{DOMAIN} {name}",
manufacturer=DEFAULT_BRAND, manufacturer=DEFAULT_BRAND,
) )
def update(self) -> None: async def async_update(self) -> None:
"""Update the state of the device.""" """Update the state of the device."""
if self.data.check_if_ok_to_update(): if self.data.check_if_ok_to_update():
_LOGGER.debug( _LOGGER.debug(
@ -64,23 +68,38 @@ class BlinkSyncModule(AlarmControlPanelEntity):
self._name, self._name,
self.data, self.data,
) )
self.data.refresh() try:
await self.data.refresh(force=True)
self._attr_available = True
except asyncio.TimeoutError:
self._attr_available = False
_LOGGER.info("Updating State of Blink Alarm Control Panel '%s'", self._name) _LOGGER.info("Updating State of Blink Alarm Control Panel '%s'", self._name)
self._attr_state = (
STATE_ALARM_ARMED_AWAY if self.sync.arm else STATE_ALARM_DISARMED
)
self.sync.attributes["network_info"] = self.data.networks self.sync.attributes["network_info"] = self.data.networks
self.sync.attributes["associated_cameras"] = list(self.sync.cameras) self.sync.attributes["associated_cameras"] = list(self.sync.cameras)
self.sync.attributes[ATTR_ATTRIBUTION] = DEFAULT_ATTRIBUTION self.sync.attributes[ATTR_ATTRIBUTION] = DEFAULT_ATTRIBUTION
self._attr_extra_state_attributes = self.sync.attributes self._attr_extra_state_attributes = self.sync.attributes
def alarm_disarm(self, code: str | None = None) -> None: @property
"""Send disarm command.""" def state(self) -> StateType:
self.sync.arm = False """Return state of alarm."""
self.sync.refresh() return STATE_ALARM_ARMED_AWAY if self.sync.arm else STATE_ALARM_DISARMED
def alarm_arm_away(self, code: str | None = None) -> None: async def async_alarm_disarm(self, code: str | None = None) -> None:
"""Send disarm command."""
try:
await self.sync.async_arm(False)
await self.sync.refresh(force=True)
self.async_write_ha_state()
except asyncio.TimeoutError:
self._attr_available = False
async def async_alarm_arm_away(self, code: str | None = None) -> None:
"""Send arm command.""" """Send arm command."""
self.sync.arm = True try:
self.sync.refresh() await self.sync.async_arm(True)
await self.sync.refresh(force=True)
self.async_write_ha_state()
except asyncio.TimeoutError:
self._attr_available = False

View File

@ -52,7 +52,7 @@ async def async_setup_entry(
for camera in data.cameras for camera in data.cameras
for description in BINARY_SENSORS_TYPES for description in BINARY_SENSORS_TYPES
] ]
async_add_entities(entities) async_add_entities(entities, update_before_add=True)
class BlinkBinarySensor(BinarySensorEntity): class BlinkBinarySensor(BinarySensorEntity):
@ -75,15 +75,16 @@ class BlinkBinarySensor(BinarySensorEntity):
model=self._camera.camera_type, model=self._camera.camera_type,
) )
def update(self) -> None: @property
def is_on(self) -> bool | None:
"""Update sensor state.""" """Update sensor state."""
state = self._camera.attributes[self.entity_description.key] is_on = self._camera.attributes[self.entity_description.key]
_LOGGER.debug( _LOGGER.debug(
"'%s' %s = %s", "'%s' %s = %s",
self._camera.attributes["name"], self._camera.attributes["name"],
self.entity_description.key, self.entity_description.key,
state, is_on,
) )
if self.entity_description.key == TYPE_BATTERY: if self.entity_description.key == TYPE_BATTERY:
state = state != "ok" is_on = is_on != "ok"
self._attr_is_on = state return is_on

View File

@ -1,7 +1,10 @@
"""Support for Blink system camera.""" """Support for Blink system camera."""
from __future__ import annotations from __future__ import annotations
import asyncio
from collections.abc import Mapping
import logging import logging
from typing import Any
from requests.exceptions import ChunkedEncodingError from requests.exceptions import ChunkedEncodingError
@ -29,7 +32,7 @@ async def async_setup_entry(
BlinkCamera(data, name, camera) for name, camera in data.cameras.items() BlinkCamera(data, name, camera) for name, camera in data.cameras.items()
] ]
async_add_entities(entities) async_add_entities(entities, update_before_add=True)
platform = entity_platform.async_get_current_platform() platform = entity_platform.async_get_current_platform()
platform.async_register_entity_service(SERVICE_TRIGGER, {}, "trigger_camera") platform.async_register_entity_service(SERVICE_TRIGGER, {}, "trigger_camera")
@ -56,19 +59,25 @@ class BlinkCamera(Camera):
_LOGGER.debug("Initialized blink camera %s", self.name) _LOGGER.debug("Initialized blink camera %s", self.name)
@property @property
def extra_state_attributes(self): def extra_state_attributes(self) -> Mapping[str, Any] | None:
"""Return the camera attributes.""" """Return the camera attributes."""
return self._camera.attributes return self._camera.attributes
def enable_motion_detection(self) -> None: async def async_enable_motion_detection(self) -> None:
"""Enable motion detection for the camera.""" """Enable motion detection for the camera."""
self._camera.arm = True try:
self.data.refresh() await self._camera.async_arm(True)
await self.data.refresh(force=True)
except asyncio.TimeoutError:
self._attr_available = False
def disable_motion_detection(self) -> None: async def async_disable_motion_detection(self) -> None:
"""Disable motion detection for the camera.""" """Disable motion detection for the camera."""
self._camera.arm = False try:
self.data.refresh() await self._camera.async_arm(False)
await self.data.refresh(force=True)
except asyncio.TimeoutError:
self._attr_available = False
@property @property
def motion_detection_enabled(self) -> bool: def motion_detection_enabled(self) -> bool:
@ -76,21 +85,24 @@ class BlinkCamera(Camera):
return self._camera.arm return self._camera.arm
@property @property
def brand(self): def brand(self) -> str | None:
"""Return the camera brand.""" """Return the camera brand."""
return DEFAULT_BRAND return DEFAULT_BRAND
def trigger_camera(self): async def trigger_camera(self) -> None:
"""Trigger camera to take a snapshot.""" """Trigger camera to take a snapshot."""
self._camera.snap_picture() try:
self.data.refresh() await self._camera.snap_picture()
self.async_schedule_update_ha_state(force_refresh=True)
except asyncio.TimeoutError:
pass
def camera_image( def camera_image(
self, width: int | None = None, height: int | None = None self, width: int | None = None, height: int | None = None
) -> bytes | None: ) -> bytes | None:
"""Return a still image response from the camera.""" """Return a still image response from the camera."""
try: try:
return self._camera.image_from_cache.content return self._camera.image_from_cache
except ChunkedEncodingError: except ChunkedEncodingError:
_LOGGER.debug("Could not retrieve image for %s", self._camera.name) _LOGGER.debug("Could not retrieve image for %s", self._camera.name)
return None return None

View File

@ -16,10 +16,11 @@ from homeassistant.const import (
CONF_SCAN_INTERVAL, CONF_SCAN_INTERVAL,
CONF_USERNAME, CONF_USERNAME,
) )
from homeassistant.core import callback from homeassistant.core import HomeAssistant, callback
from homeassistant.data_entry_flow import FlowResult from homeassistant.data_entry_flow import FlowResult
from homeassistant.exceptions import HomeAssistantError from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers import selector from homeassistant.helpers import selector
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from homeassistant.helpers.schema_config_entry_flow import ( from homeassistant.helpers.schema_config_entry_flow import (
SchemaFlowFormStep, SchemaFlowFormStep,
SchemaOptionsFlowHandler, SchemaOptionsFlowHandler,
@ -49,23 +50,23 @@ OPTIONS_FLOW = {
} }
def validate_input(auth: Auth) -> None: async def validate_input(auth: Auth) -> None:
"""Validate the user input allows us to connect.""" """Validate the user input allows us to connect."""
try: try:
auth.startup() await auth.startup()
except (LoginError, TokenRefreshFailed) as err: except (LoginError, TokenRefreshFailed) as err:
raise InvalidAuth from err raise InvalidAuth from err
if auth.check_key_required(): if auth.check_key_required():
raise Require2FA raise Require2FA
def _send_blink_2fa_pin(auth: Auth, pin: str | None) -> bool: async def _send_blink_2fa_pin(hass: HomeAssistant, auth: Auth, pin: str) -> bool:
"""Send 2FA pin to blink servers.""" """Send 2FA pin to blink servers."""
blink = Blink() blink = Blink(session=async_get_clientsession(hass))
blink.auth = auth blink.auth = auth
blink.setup_login_ids() blink.setup_login_ids()
blink.setup_urls() blink.setup_urls()
return auth.send_auth_key(blink, pin) return await auth.send_auth_key(blink, pin)
class BlinkConfigFlow(ConfigFlow, domain=DOMAIN): class BlinkConfigFlow(ConfigFlow, domain=DOMAIN):
@ -91,11 +92,15 @@ class BlinkConfigFlow(ConfigFlow, domain=DOMAIN):
"""Handle a flow initiated by the user.""" """Handle a flow initiated by the user."""
errors = {} errors = {}
if user_input is not None: if user_input is not None:
self.auth = Auth({**user_input, "device_id": DEVICE_ID}, no_prompt=True) self.auth = Auth(
{**user_input, "device_id": DEVICE_ID},
no_prompt=True,
session=async_get_clientsession(self.hass),
)
await self.async_set_unique_id(user_input[CONF_USERNAME]) await self.async_set_unique_id(user_input[CONF_USERNAME])
try: try:
await self.hass.async_add_executor_job(validate_input, self.auth) await validate_input(self.auth)
return self._async_finish_flow() return self._async_finish_flow()
except Require2FA: except Require2FA:
return await self.async_step_2fa() return await self.async_step_2fa()
@ -122,12 +127,9 @@ class BlinkConfigFlow(ConfigFlow, domain=DOMAIN):
"""Handle 2FA step.""" """Handle 2FA step."""
errors = {} errors = {}
if user_input is not None: if user_input is not None:
pin: str | None = user_input.get(CONF_PIN) pin: str = str(user_input.get(CONF_PIN))
try: try:
assert self.auth valid_token = await _send_blink_2fa_pin(self.hass, self.auth, pin)
valid_token = await self.hass.async_add_executor_job(
_send_blink_2fa_pin, self.auth, pin
)
except BlinkSetupError: except BlinkSetupError:
errors["base"] = "cannot_connect" errors["base"] = "cannot_connect"
except Exception: # pylint: disable=broad-except except Exception: # pylint: disable=broad-except

View File

@ -20,5 +20,5 @@
"documentation": "https://www.home-assistant.io/integrations/blink", "documentation": "https://www.home-assistant.io/integrations/blink",
"iot_class": "cloud_polling", "iot_class": "cloud_polling",
"loggers": ["blinkpy"], "loggers": ["blinkpy"],
"requirements": ["blinkpy==0.21.0"] "requirements": ["blinkpy==0.22.2"]
} }

View File

@ -1,12 +1,15 @@
"""Support for Blink system camera sensors.""" """Support for Blink system camera sensors."""
from __future__ import annotations from __future__ import annotations
from datetime import date, datetime
from decimal import Decimal
import logging import logging
from homeassistant.components.sensor import ( from homeassistant.components.sensor import (
SensorDeviceClass, SensorDeviceClass,
SensorEntity, SensorEntity,
SensorEntityDescription, SensorEntityDescription,
SensorStateClass,
) )
from homeassistant.config_entries import ConfigEntry from homeassistant.config_entries import ConfigEntry
from homeassistant.const import ( from homeassistant.const import (
@ -17,6 +20,7 @@ from homeassistant.const import (
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.helpers.device_registry import DeviceInfo from homeassistant.helpers.device_registry import DeviceInfo
from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.typing import StateType
from .const import DEFAULT_BRAND, DOMAIN, TYPE_TEMPERATURE, TYPE_WIFI_STRENGTH from .const import DEFAULT_BRAND, DOMAIN, TYPE_TEMPERATURE, TYPE_WIFI_STRENGTH
@ -28,6 +32,7 @@ SENSOR_TYPES: tuple[SensorEntityDescription, ...] = (
native_unit_of_measurement=UnitOfTemperature.FAHRENHEIT, native_unit_of_measurement=UnitOfTemperature.FAHRENHEIT,
device_class=SensorDeviceClass.TEMPERATURE, device_class=SensorDeviceClass.TEMPERATURE,
entity_category=EntityCategory.DIAGNOSTIC, entity_category=EntityCategory.DIAGNOSTIC,
state_class=SensorStateClass.MEASUREMENT,
), ),
SensorEntityDescription( SensorEntityDescription(
key=TYPE_WIFI_STRENGTH, key=TYPE_WIFI_STRENGTH,
@ -35,6 +40,7 @@ SENSOR_TYPES: tuple[SensorEntityDescription, ...] = (
native_unit_of_measurement=SIGNAL_STRENGTH_DECIBELS_MILLIWATT, native_unit_of_measurement=SIGNAL_STRENGTH_DECIBELS_MILLIWATT,
device_class=SensorDeviceClass.SIGNAL_STRENGTH, device_class=SensorDeviceClass.SIGNAL_STRENGTH,
entity_category=EntityCategory.DIAGNOSTIC, entity_category=EntityCategory.DIAGNOSTIC,
state_class=SensorStateClass.MEASUREMENT,
), ),
) )
@ -50,7 +56,7 @@ async def async_setup_entry(
for description in SENSOR_TYPES for description in SENSOR_TYPES
] ]
async_add_entities(entities) async_add_entities(entities, update_before_add=True)
class BlinkSensor(SensorEntity): class BlinkSensor(SensorEntity):
@ -76,10 +82,11 @@ class BlinkSensor(SensorEntity):
model=self._camera.camera_type, model=self._camera.camera_type,
) )
def update(self) -> None: @property
def native_value(self) -> StateType | date | datetime | Decimal:
"""Retrieve sensor data from the camera.""" """Retrieve sensor data from the camera."""
try: try:
self._attr_native_value = self._camera.attributes[self._sensor_key] native_value = self._camera.attributes[self._sensor_key]
_LOGGER.debug( _LOGGER.debug(
"'%s' %s = %s", "'%s' %s = %s",
self._camera.attributes["name"], self._camera.attributes["name"],
@ -87,7 +94,8 @@ class BlinkSensor(SensorEntity):
self._attr_native_value, self._attr_native_value,
) )
except KeyError: except KeyError:
self._attr_native_value = None native_value = None
_LOGGER.error( _LOGGER.error(
"%s not a valid camera attribute. Did the API change?", self._sensor_key "%s not a valid camera attribute. Did the API change?", self._sensor_key
) )
return native_value

View File

@ -539,7 +539,7 @@ bleak==0.21.1
blebox-uniapi==2.2.0 blebox-uniapi==2.2.0
# homeassistant.components.blink # homeassistant.components.blink
blinkpy==0.21.0 blinkpy==0.22.2
# homeassistant.components.bitcoin # homeassistant.components.bitcoin
blockchain==1.4.4 blockchain==1.4.4

View File

@ -460,7 +460,7 @@ bleak==0.21.1
blebox-uniapi==2.2.0 blebox-uniapi==2.2.0
# homeassistant.components.blink # homeassistant.components.blink
blinkpy==0.21.0 blinkpy==0.22.2
# homeassistant.components.bluemaestro # homeassistant.components.bluemaestro
bluemaestro-ble==0.2.3 bluemaestro-ble==0.2.3

View File

@ -1,5 +1,5 @@
"""Test the Blink config flow.""" """Test the Blink config flow."""
from unittest.mock import Mock, patch from unittest.mock import AsyncMock, Mock, patch
from blinkpy.auth import LoginError from blinkpy.auth import LoginError
from blinkpy.blinkpy import BlinkSetupError from blinkpy.blinkpy import BlinkSetupError
@ -268,10 +268,10 @@ async def test_options_flow(hass: HomeAssistant) -> None:
) )
config_entry.add_to_hass(hass) config_entry.add_to_hass(hass)
mock_auth = Mock( mock_auth = AsyncMock(
startup=Mock(return_value=True), check_key_required=Mock(return_value=False) startup=Mock(return_value=True), check_key_required=Mock(return_value=False)
) )
mock_blink = Mock() mock_blink = AsyncMock()
with patch("homeassistant.components.blink.Auth", return_value=mock_auth), patch( with patch("homeassistant.components.blink.Auth", return_value=mock_auth), patch(
"homeassistant.components.blink.Blink", return_value=mock_blink "homeassistant.components.blink.Blink", return_value=mock_blink
@ -293,7 +293,6 @@ async def test_options_flow(hass: HomeAssistant) -> None:
result["flow_id"], result["flow_id"],
user_input={"scan_interval": 5}, user_input={"scan_interval": 5},
) )
assert result["type"] == data_entry_flow.FlowResultType.CREATE_ENTRY assert result["type"] == data_entry_flow.FlowResultType.CREATE_ENTRY
assert result["data"] == {"scan_interval": 5} assert result["data"] == {"scan_interval": 5}
await hass.async_block_till_done() await hass.async_block_till_done()